WPGraphQL v2.0 Is Coming – Here’s What You Need to Know


We’re excited to announce that WPGraphQL v2.0 is coming soon! This release brings key improvements such as better performance, stricter validation, and more flexibility for developers.

This change introduces breaking changes that may affect your site or project.

For technical details on breaking changes and developer recommendations, read the WPGraphQL v2.0 Technical Update Guide.

Release Timeline


We plan to release WPGraphQL v2.0 in the first week of February. This timing allows developers to prepare after the holiday and start-of-year rush.

We’ve already opened a Beta Pull Request for testing. If you’d like to get an early look, test your projects against the beta and let us know your feedback!

Breaking Changes


WPGraphQL v2.0 will require PHP 7.4 or newer. Please ensure your server is running PHP 7.4 or newer before installing the new version.

We’re upgrading WPGraphQL’s underlying GraphQL engine, graphql-php, from v14.11.10 to v15.8.1. This brings modern improvements but also introduces changes developers need to be aware of.

We’ve written up a more technical guide that gets into more detail about the changes.

What you should do

  • Check your PHP version: Ensure your site runs PHP 7.4+
  • Test in a staging environment: Test the update in a staging environment before applying it to your live site.
  • Check for compatibility: Ensure custom plugins or WPGraphQL extensions are compatible with v2.0.

Looking Ahead

While we work toward the final v2.0 release, we’ll continue adding new features and improvements. Some features currently in development include:

  • Extensions Page: An enhanced admin experience for managing WPGraphQL extensions.
  • Semantic Version Check: A system to reduce the risk of auto-updates causing breaking changes.

Learn More & Stay Informed

For developer-focused technical details, visit our WPGraphQL v2.0 Technical Update Guide. We’ll share more updates as the release approaches.

WPGraphQL v2.0 Technical Update & Breaking Changes

WPGraphQL v2.0 is coming soon, featuring an upgrade to its underlying GraphQL engine, graphql-php, from v14.11.10 to v15.8.1.

This upgrade brings better performance, stricter validation, and enhanced extensibility. However, it also introduces breaking changes that may impact developers extending WPGraphQL or maintaining custom plugins.

This guide highlights technical changes, developer recommendations, and actions needed to prepare your projects.

PHP Version Requirement:

WPGraphQL v2.0 will require PHP 7.4 or newer, raising the minimum requirement from PHP 7.1.
This change follows the adoption of graphql-php v15+, which also requires PHP 7.4+. Ensure your hosting environment is updated before testing the beta or upgrading to v2.0.

By adopting the latest graphql-php, WPGraphQL inherits several breaking changes that may impact developers extending WPGraphQL. Below, we’ve highlighted the most important changes, though we also recommend reviewing the graphql-php release notes for more detail.

Key Breaking Changes:

Error Response Changes:

  • “category” field removal on errors: The “category” field has been removed from GraphQL error responses. If your application depends on this field, adjust your error-handling logic accordingly.
  • debug entries moved: graphql-php now places debug information under the extensions key of the error response. Ensure your application properly handles this structure.
  • serialization errors: Errors during leaf value serialization will now throw SerializationError, replacing previously thrown generic errors.

Schema and Type Management Updates:

  • Topological Schema Ordering: graphql-php now orders schema definitions based on type dependencies and user-defined types. Be aware that this could change the output of introspection queries, especially when dynamically generating types.
  • Standard Type Enforcement: Overriding built-in GraphQL Types in the schema is no longer supported. Consider using custom types instead of overriding built-in types directly.

Input and Enum Parsing Enhancements:

  • Lazy Loading for Input Objects and Enums: graphql-php now supports lazy-loading for input objects and enums. This can improve performance for schemas with complex, deeply nested types. Developers defining custom types should adjust type definitions if needed.

Server and HTTP Behavior Adjustments:

  • Request Validation: Requests with invalid JSON will now trigger a RequestError instead of a generic error. This makes debugging request payload issues easier by returning clear error messages early in the request lifecycle.

Recommended Actions:

  • Upgrade PHP: Ensure your environment runs PHP 7.4+ before updating WPGraphQL.
  • Review Error Handling: Update how your application handles GraphQL errors, including changes to the category field and the new location of debug entries.
  • Test Plugin Compatibility: Test any custom plugins, WPGraphQL extensions, and GraphQL queries in a staging environment. Ensure compatibility before deploying to production.

Resources and Links:

WordPress Meets WASM: Full Power of the CMS in Any App

Last week at WASMCon 2024, I had the privilege of speaking about something I’m incredibly passionate about: using WordPress in non-traditional ways. My talk, “WordPress Meets WASM: Full Power of the CMS in Any App,” explored how WordPress, a platform many associate with traditional websites, can now be embedded and run virtually anywhere, thanks to technologies like WebAssembly (WASM).

Below is a recap for those who prefer reading over watching videos.

A Journey of Non-Traditional WordPress Use

I began by sharing a personal story from 2006, when I stumbled upon a Flash-based website for the marketing agency Leo Burnett that inspired me to learn Flash development.

(NOTE: You can view the 2006 version of the Leo Burnett website on Archive.org)

That curiosity eventually led me to WordPress when I needed a way to manage content in a Flash site without requiring the end user to know Flash. Back then, I discovered WordPress and its XML-RPC API that could be used to connect a Flash front end to a WordPress back end. While that project didn’t go far, it was my first step into the world of WordPress, and it changed the trajectory of my career.

Fast forward to 2016, I created WPGraphQL, a free, open-source plugin that allows developers to use WordPress as a GraphQL API. This opens up countless possibilities for building decoupled or headless front ends with frameworks like Next.js, Gatsby, and Astro, or even native mobile apps. The content lives in WordPress, but how it’s rendered is entirely up to the developer.

Enter WordPress Playground

While WPGraphQL has made headless WordPress more accessible, developers still face challenges in quickly setting up environments to test plugins or content workflows. Traditionally, this would require setting up a local server with PHP, MySQL, and Apache.

WordPress Playground changes everything.

WordPress Playground leverages WebAssembly to run WordPress entirely in the browser — no server required. PHP is compiled to WebAssembly (PHP-WASM), MySQL is replaced by SQLite, and server functionality is replaced by JavaScript APIs. This means anyone can instantly spin up a WordPress instance in their browser, making it easier than ever to demo plugins or test workflows.

Real-World Use Cases

I highlighted two practical use cases during my talk:

Plugin Demos and Live Previews

With WordPress Playground, plugin developers like me can offer live previews directly in the browser. For example, if you’re browsing the WordPress.org repository, a plugin could have a “Live Preview” button. Clicking it would launch a full WordPress instance with the plugin activated, allowing users to test it instantly—no setup required.

Markdown Documentation Editing

I use WordPress to manage my blog content, but I prefer keeping my documentation in Markdown files close to the codebase. WordPress Playground enables me to bridge the gap between these workflows. I can import Markdown files from a GitHub repository into WordPress running in the browser, make edits using a familiar CMS interface, and export the updated files back to GitHub as a pull request. This workflow combines the strengths of both traditional CMSs and modern version control systems.

Beyond the Browser

WordPress Playground isn’t limited to browser-based workflows. It can run in:

  • Terminal applications
  • Native mobile apps
  • Desktop environments
  • Embedded in web pages
  • Edge servers for near-instant content delivery

This versatility allows WordPress to adapt to countless use cases, redefining how and where it can be used.

A Call to Action

WordPress Playground is a free, open-source project that pushes the boundaries of what’s possible with WordPress. Whether you’re a long-time WordPress user or new to the platform, I encourage you to explore how this technology can power your projects or inspire you to build something new. For more information, visit WordPress Playground.

Thank you to everyone who attended my talk or watched it online. If you’d like to chat more, find me on X/Twitter at @jasonbahl.

Try WPGraphQL Instantly with WordPress Playground Integration

We’re excited to announce that WPGraphQL v1.29.2 now supports Live Preview functionality on WordPress.org, powered by WordPress Playground. This integration makes it easier than ever for developers to experience WPGraphQL firsthand without any installation or setup required.

Instant Access to GraphQL in WordPress

Starting with version 1.29.2, visitors to the WPGraphQL plugin page on WordPress.org can click the “Live Preview” link to instantly launch a fully functional WordPress environment in their browser.

This environment comes pre-loaded with WPGraphQL and provides immediate access to the GraphiQL IDE.

Zero Setup Required

Thanks to WordPress Playground, trying out WPGraphQL is now as simple as:

  1. Visit the WPGraphQL plugin page on WordPress.org
  2. Click the “Live Preview” link
  3. Start exploring the GraphQL API in your browser

All you need is a web browser and an internet connection – no local development environment, no plugin installation, and no configuration required.

Perfect for Quick Evaluation

This new feature is particularly valuable for:

  • Developers evaluating WPGraphQL for their projects
  • Teams wanting to quickly demonstrate WPGraphQL capabilities
  • Learning and exploring GraphQL queries in a WordPress context
  • Testing compatibility with WordPress core features

What’s Next?

While the Live Preview feature provides a great way to explore WPGraphQL’s capabilities, remember that for production use, you’ll want to install WPGraphQL directly on your WordPress site. The Live Preview environment is perfect for testing and exploration, but it’s temporary and resets between sessions.

We’re excited to see how this new feature helps more developers discover and experiment with WPGraphQL. Give it a try and let us know what you think!

Have you tried the new Live Preview feature? We’d love to hear your feedback in the WPGraphQL Discord below or on GitHub.

WPGraphQL Becomes a Canonical Plugin: My Move to Automattic

Before diving into the details of this announcement, I want to address something important. I am, like all of you, a human being. Given the current tensions in the WordPress ecosystem, my decision to move from WP Engine to Automattic might evoke strong feelings. Whether you agree or disagree with this decision, please recognize me as a human. Please treat me with respect, even if you strongly oppose my choices. The WordPress community is one I care deeply about, and we all benefit from respectful dialogue, no matter our differences.

With that said, I’m excited to announce that after 3.5 wonderful years at WP Engine, I’ve accepted an offer with Automattic to continue my work on WPGraphQL as it transitions into becoming a canonical community plugin on WordPress.org.

Reflecting on My Time at WP Engine

I’ve truly enjoyed my time at WP Engine. I’ve had the opportunity to publish over 100 releases of WPGraphQL, re-architect WPGraphQL for ACF, introduce WPGraphQL Smart Cache, and help countless users successfully deploy decoupled sites powered by WPGraphQL. I’ve made great friends at WP Engine and hope to continue collaborating with them in the open-source ecosystem for years.

That said, as someone deeply committed to Free Open Source Software (FOSS), the economics around open-source projects can take time to balance. While WP Engine has treated me well personally, the focus on open-source contributions from the organization has declined during my time there. My time was also reallocated away from WPGraphQL and community projects as internal initiatives took priority. Any company needs to focus on internal growth. Still, I believe there’s a conversation to be had about how that fits into the broader open-source community and whether it supports long-term success. Several other former colleagues and I tried to talk about how WP Engine could better participate in Open Source, but those conversations didn’t always gain traction. Hopefully, those conversations can begin again. I don’t have the answers, but I do know there is a problem.

Why Automattic? Why Now?

WPGraphQL has always been and will continue to be free open-source software. Automattic’s track record as a fantastic steward of open-source projects is well known, and I believe it’s the perfect home for WPGraphQL. This move will continue to keep WPGraphQL free and open source while bringing more visibility and contributions from the community.

There is so much untapped potential in the decoupled WordPress ecosystem. While WPGraphQL is already trusted to power thousands of sites, this move will allow us to make even more progress in helping teams build modern API-driven websites and applications with WordPress. Becoming a canonical community plugin will open up new opportunities for collaboration, growth, and innovation across the ecosystem.

On Matt Mullenweg’s Recent Actions

There will likely be assumptions that by joining Automattic, I’m fully endorsing every action Matt Mullenweg has taken recently. This is not the case. I’ve told Matt directly that I don’t agree with everything he’s done — and he has welcomed the disagreement. For example, I don’t think WordCamp US was the right time or place for his speech. I also do not agree with blocking WP Engine customers from WordPress.org without more notice. Should WordPress.org be required to remain a free service forever? Not necessarily. But should long-time users receive advance notice when significant changes are made? I think so.

I could probably list many other things that I don’t agree with Matt on but the reality is that I’ve never worked at a company where I’ve agreed with every single action their leaders have taken. 

Leadership often involves making uncomfortable choices that others might not fully understand or agree with. His historical actions have led WordPress to its current success, and I believe that bold moves—though uncomfortable—are sometimes necessary to ensure the long-term future of WordPress. As a maintainer of open-source software, my livelihood depends on people like Matt, who are willing to keep WordPress relevant in the years to come.

WPGraphQL is becoming a canonical WordPress community plugin

Automattic has a long record of nurturing open-source projects used by millions. By becoming a canonical project—similar to WP-CLI, Gutenberg, or the WP REST API before it was merged into core—I anticipate a lot of support from Automattic and the wider community. I believe this move will bring tangible benefits not just to WPGraphQL users but also to the hosts and developers building decoupled WordPress experiences.

There’s much more to come, and I’m excited to continue building tools and features that help teams succeed with modern web development using WordPress and WPGraphQL. Together, we can achieve great things for the entire ecosystem!

Migrating the WPGraphQL Slack Community to Discord

Hello WPGraphQL Community,

We’re excited to announce that we’re moving from Slack to Discord! This change will enhance our communication and community experience.

📣 Why Move to Discord?

No More Lost History: All message history from Slack (since 2017!) has been migrated and is searchable in Discord.
Dedicated Channels: Specific topics for streamlined conversations.
Voice Channels: Engage with others directly through voice chats.
Community Events: Stay updated on upcoming events, webinars, and more.
Enhanced Moderation: Keeping the community safe and welcoming.
Resource Sharing: More tools for organizing content and sharing ideas.

💬 Join Us on Discord!

  • Download Discord (desktop, mobile) or use in the browser.
  • Click this invite link: https://discord.gg/AGVBqqyaUY
  • Introduce yourself in #introductions channel and start exploring!

Let’s make this move together and continue growing the WPGraphQL community!

Best,

Jason Bahl
Creator and Maintainer of WPGraphQL

Announcing the WPGraphQL REPL

“What is a REPL?”, “Who are you?”, and “What have you done with Jason?” are all good questions you might be asking. I will start with the most important one. Hello, I am Alex aka “moonmeister”! I have been around for a while and you might have seen me in Slack or chatted with me when I worked at Gatsby or WP Engine. I recently started wpdecoupled.dev where I write about all things decoupled/headless WordPress. I mostly work on the JavaScript side of code and leave the PHP to folks like Jason Bahl and David Levine.

I am happy to announce the initial release of the WPGraphQL REPL. In this post, I will be talking about what it is I built and why I built it.

TL;DR

I built a REPL for WPGraphQL. A REPL helps us accelerate learning, bug finding, and more by allowing you to spin up a working WP instance in the time it takes you to load a web page. You can find it at repl.wpgraphql.com. Use it, love it, share it. There are lots of features yet to build and bugs to find. Go wild, and come help build a better future on the repo.

What’s a REPL?

For those who are not familiar, a REPL (Read-Evaluate-Print Loop) is a really valuable tool for any ecosystem. A REPL can be used to reproduce a bug, teach the basics of the ecosystem, experiment, and more quickly collaborate. Still confused? Let’s dig deeper!

Bugs

Okay, you are building with WPGraphQL and something is not working, you have found a bug. You move from your JavaScript client to GraphiQL in the WP Admin. You see the bug happening there too. You now know not to blame JavaScript. What do you do next? My next step is to replicate the bug in a “minimal reproduction”.

Get it? REPL…replicate…it is an acronym and an abbreviation!

The idea of a minimal reproduction is to eliminate variables. No, not the let foo = "bar"; kind. What PHP, WP, and WPGraphQL are you running? What other plugins are installed and what are their versions? These are all questions that help us track down the source of an issue. The reason maintainers often ask folks to create their own minimal reproductions is the act of doing so helps folks break down an issue, eliminate variables, and solve their own problem.

Given the numerous versions of PHP, WP, and WPGraphQL this can be very time-consuming. Usually, PHP and WP versions can be ignored, but not always. Luckily, we have had Local for years which helps accelerate this process, but it is still quite time-consuming. Inevitably, I end up cowboy coding on my production instance cause it is just faster.

This is where a REPL comes in. If I have a place I can quickly get WP running with WPGraphQL and switch between a variety of versions and test code on the fly, I am going to get to a minimal reproduction and an answer faster.

Experimenting

I have often run into situations where I want to try a new plugin, or just delve into an aspect of WPGraphQL I am not super familiar with. GraphiQL in my WP instance can be helpful here. But once again it is slow to spin up WP, even in Local. I just want something fast so that I can grab the one plugin I need and GO! I want to get back to the building by accelerating the learning process.

Learning/Teaching

There are many situations where I need to share the code with others. This could be and embed in a blog post or just a quick reply to someone trying to figure out WPGraphQL in Slack. Either way, I want a fast and controlled way to write code and hand it to them in a way they can see, understand and start their own experimentation.

Are you seeing the theme…speed. This is what REPLs provide us…it gets us away from “it works on my machine”…it gets us away from Docker hell…it gets us to what we really need, a reproducible and flexible environment where I can focus on my specific issue and not worry about my LAMP stack, breaking production, or anything other than the issue at hand.

My Dream Realized

For as long as I can remember, I have wanted a REPL for the WPGraphQL ecosystem. I knew it was theoretically possible but practically, I did not have the skills to implement it. Then I saw an announcement from the WordPress community on https://developer.wordpress.org/playground/.

My dream was possible. I no longer needed to learn how to run PHP and WP in WASM, I could just build the UI and control interface to the specific needs of the WPGraphQL community. After a couple of 4 am coding sessions, I had something I could conceive of sharing. Several weeks of random coding sessions later and an initial release is here.

Getting Started

After navigating to https://repl.wpgraphql.com WP will load within ~20 seconds. First to load will be the familiar GraphiQL instance that comes with WPGraphQL. On the left panel, you are currently able to navigate to a new URL and change the running PHP and WP versions.

Currently, the state (db and filesystem) within WP is ephemeral and will be lost with a page load. Work is being done by the WordPress Playground team to persist the state across refresh. If you want to save your state or load your configured WP into a LAMP stack outside of the browser you can use the download button in the upper right to download a zip of your site. I hope to have an upload button soon to be able to reload that same file back in later.

Please, go check out https://repl.wpgraphql.com, I hope you come to value a good REPL as much as I do. Enjoy! If you run into missing features or bugs please head over to the Github repo and open issues, request features, and contribute code to make this better! We also have a #repl channel on the WPGraphQL Slack!

Customizing GraphQL Cache Keys.

WPGraphQL Smart Cache is quickly becoming the standard solution for fast and accurate data when using WPGraphQL for decoupled applications. Since launching in December 2022, it’s grown to 500+ active users!

While it solves a lot of problems for a lot of users, it’s not perfect and might require customization.

In this blog post, we’re going to look at how to customize the keys that are returned with GraphQL responses, which customizes how the GraphQL documents are tagged in the cache, and thus customizes what events will purge the cached document.

How WPGraphQL Smart Cache Invalidation works

Before we dive into customizing WPGraphQL Smart Cache keys, let’s take a look at how caching and cache invalidation with WPGraphQL Smart Cache works.

Understanding how GraphQL Queries are Cached and “Tagged”

When making a query to a WPGraphQL-powered endpoint, WPGraphQL will return headers that caching clients can use to tag the cached document with.

To see this in action, I will use a local WordPress install (powered by Local). In this WordPress install, I have the following plugins installed and activated:

In the WordPress dashboard, I will navigate to the GraphiQL IDE.

I will open Chrome’s dev tools and select the “Network” tab.

Then, in the GraphiQL IDE, I will execute the following query:

query GetPosts {
  posts {
    nodes {
      title
      uri
    }
  }
}

And I see the following response:

Screenshot of the GraphiQL IDE executing a query for posts and showing the JSON response from the query.

With the Network tools open, we can click on the request and inspect the headers:

Here, we see the X-GraphQL-Keys header with a list of keys. If this query were made via a GET request to a site hosted on a host that supports it, the response would be cached and “tagged” with these keys.
For this particular query, we see the following keys:

  • 4382426a7bebd62479da59a06d90ceb12e02967d342afb7518632e26b95acc6f (hash of the query)
  • graphql:Query (operation type)
  • operation:GetPosts (operation name)
  • list:post (identifies the query asked for a list of posts)
  • cG9zdDox (id of the node(s) resolving. In this query response, only 1 node was resolved)

The document will be tagged with these keys, and in turn will be purged (deleted) from the cache if a purge event for one of those tags is triggered.

Understanding Cache Invalidation with WPGraphQL Smart Cache

Above, we looked at how supported hosts will cache GraphQL responses and use the X-GraphQL-Keys to “tag” the cached document.

Now let’s take a look at how WPGraphQL Smart Cache invalidates, or “purges” these tagged documents from the cache.

WPGraphQL Smart Cache listens to events that occur in WordPress, and in response to these events, calls “purge” on specific “tags”, deleting any document from the cache that was tagged with said tag.

The simplified summary of the WPGraphQL Smart Cache invalidation strategy is as follows:

  • Publish events call purge( 'list:$type_name' )
  • Update events call purge( '$nodeId' )
  • Delete events call purge( '$nodeId' )

How Cache Invalidation and Cache Tags work together

Now that we have an idea of how cached documents are tagged, and how events in WordPress call “purge” on different tags, let’s bring it all together.

Above we identified the query keys that the document will be tagged with. And now that we understand the cache invalidation strategy, we know that the GetPosts query will be purged from the cache whenever the following events occur:

  • A new post is published ( purge( 'list:post' ) )
  • The node identified by id cG9zdDox (the “hello world” post) is updated or deleted
  • Clicking “purge cache” in the GraphQL > Settings > Cache page (`purge( ‘graphql:Query’ )` )
  • Any purge event manually added to purge based on operation name or query hash

So far, so good! This all seems reasonable. I think we would probably all want the cache for this query to be invalidated after we updated the “Hello World” post. We would want to see the updated data in the query response.

The Problem

The problem, however, is that the headers that are added to the documents might be more broad than we would like, and we might need to customize this.

For example, let’s take a look at a more complicated query:

query GetPostsWithCategoriesAndTags {
  posts {
    nodes {
      id
      title
      categories {
        nodes {
          id
          name
        }
      }
      tags {
        nodes {
          id
          name
        }
      }
    }
  }
}

In this query, we’ve asked for a list of posts, a list of categories belonging to each post and a list of tags belonging to each post.

In the response, we see that we have Posts, categories and tags (only 1 of each in this case, as we’re on a simple test site with minimal data). And if we inspect the X-GraphQL-Keys header, we will see that it’s tagged with list:post, list:category and list:tag. This is because we asked for each of these types of nodes and when a new post, category or tag is made public, it could be part of the list, so we invalidate this cache. And like magic, we could query this again and get a Cache Miss and see fresh content, like the newly published post.

This all sounds good still, but there is a problem.

The problem is that the list:category and list:tag keys will cause this document to be purged a lot more than it should be.

For example, creating a new tag and assigning it to a post that’s not shown in this query’s results will trigger the purge( 'list:tag' ) and invalidate this query, leading to more cache invalidations than we probably want.

Ideally WPGraphQL and WPGraphQL Smart Cache will be able to better identify when / when not to output the list:$type keys, but for now this is how things work, but there are ways to override it for your specific needs.

Filtering the X-GraphQL-Keys

If we want the GetPostsWithCategoriesAndTags query to NOT output the list:tag and list:category keys, we can filter the keys like so:

// Filter the keys that are returned by the Query Analyzer
add_filter( 'graphql_query_analyzer_graphql_keys', function( $graphql_keys, $return_keys, $skipped_keys, $return_keys_array, $skipped_keys_array ) {
 
        // Convert the keys from a string to an array
	$keys_array = explode( ' ', $return_keys );

        // Only apply the filter to the "GetPostsWithCategoriesAndTags" query
	if ( empty( $keys_array ) || ! in_array( 'operation:GetPostsWithCategoriesAndTags', $keys_array, true )  ) {
		return $graphql_keys;
	}

        // Remove the "list:tag" key from the headers
	if ( ( $key = array_search('list:tag', $keys_array ) ) !== false ) {
		unset( $keys_array[$key] );
	}

        // Remove the "list:category" key from the headers
	if ( ( $key = array_search('list:category', $keys_array ) ) !== false) {
		unset( $keys_array[$key] );
	}

        // Convert the array of keys back to a space separated string
	$graphql_keys['keys'] = implode( ' ', $keys_array );
	
        // Return the "filtered" $graphql_keys
	return $graphql_keys;

}, 10, 5 );

In the snippet above, we target a specific query with the operation name “GetPostsWithCategoriesAndTags” and for that query we remove the list:tag and list:category keys from being returned.

Now, we can execute the same query and inspect the headers, and we will see that list:tag and list:category are both no longer output in the X-GraphQL-Keys header.

This means that publishing new categories and tags, which triggers purge( 'list:category' ) and purge( 'list:tag' ) will not purge this document.

Success!

Now we’re getting the benefits of cached GraphQL documents, and we’re getting the document invalidated when the post is updated or deleted, but we’re letting the cache remain cached when categories or tags are created.

Conclusion

While we ultimately believe WPGraphQL Smart Cache should be, well, smarter, it might take some time to find that perfect solution that works for everyone.

In the mean time, using filters like demonstrated above, can help you tailor your WPGraphQL cache tagging and invalidation strategy to fit your specific project’s needs.

Introducing WPGraphQL Smart Cache

WPGraphQL is fast becoming the standard for building decoupled WordPress websites and applications. Most of the official WordPress boilerplates for JavaScript frameworks such as NextJS and Gatsby use WPGraphQL as the data source..

Developers are choosing WPGraphQL for decoupling their projects because of the flexibility it provides, the control it gives the client developer, the type safety, the tooling and more. The developer experience of using WPGraphQL is pretty nice.

One pain point, however, is the lack of caching.

In this post, we will go over some of the performance problems developers have ran into when using WPGraphQL, and the solutions.

The Problem: WordPress Server Timeouts

The most common way to interact with a GraphQL API is via HTTP POST requests. Most servers do not cache HTTP POST requests, meaning a POST request to WordPress will make its way to the WordPress server to be processed and returned. This requires the WordPress server to process the request and return a response.

This requires WordPress to load, execute the WordPress “bootstrap” process, load plugins, run actions and filters, load options, check for authentication and authorization, then execute the resolvers of the GraphQL request. This can cause scaling issues.

When building decoupled sites with WPGraphQL, using HTTP POST requests as the primary method of fetching data can cause your WordPress server to be overwhelmed. WebDevStudios wrote about this problem and a lot of Gatsby users have experienced this problem as well.

When a WordPress server is overwhelmed with HTTP POST requests, the server can become exhausted and run into performance issues and server timeouts.

Solution: Fetch Less

While this article is announcing WPGraphQL Smart Cache v1.0, I want to point out another solution that can work well, too.

I’ve worked with many WPGraphQL users to improve the performance of their decoupled site build times.

One of the easiest solutions to implement, is to fetch less data from WPGraphQL.

Several NextJS WordPress starter projects have popularized patterns of fetching everything from WPGraphQL in order to build static pages. This is one of the main culprits of performance issues, including the one WebDevStudios wrote about.

I’ve explained at length why this pattern of fetching data should be corrected.

The idea is that instead of fetching EVERY post and page and statically generating each page during every build of your decoupled application, you would instead only fetch what you need to build the most important pages at build time, and let the rest of the pages be built at run time, incrementally regenerated when possible.

So, instead of fetching every post and page and generating static pages for every single url at build time, we would use the WPGraphQL nodeByUri query to fetch the node for the uri at run time and incrementally build static pages at run-time, then re-generate them using ISR.

I also paired with Colby Fayock to refactor his Headless WordPress Starter project to use NextJS Incremental Static Regeneration (pull request here), leading to far fewer requests to the WordPress server.

This leads to faster build times, much fewer requests to the WordPress server, and a better experience for content creators publishing in WordPress as their changes will be live on the decoupled front-end much faster. (see a demo of publishing content on WPGraphQL.com)

By implementing Incremental Static Regeneration with the nodeByUri query, I’ve helped customers on WP Engine Atlas get their NextJS build times from more than 5 minutes, to less than 20 seconds!

Solution: WPGraphQL Smart Cache

WPGraphQL Smart Cache is a free open-source WordPress plugin that brings support for Network Cache, Object Cache, Cache Invalidation and Persisted Queries to WPGraphQL.

Below we’ll look at how WPGraphQL Smart Cache can greatly reduce response times from your WPGraphQL API and reduce build times of your decoupled sites.

You can use WPGraphQL Smart Cache and benefit greatly even if you don’t / can’t implement Incremental Static Regeneration as discussed in the section above.

Below we will look at how WPGraphQL Smart Cache helps reduce build times and alleviates pressure on your WordPress server.

Network Cache

When I refer to “Network Cache” I’m referring to caching when using HTTP GET requests.

WPGraphQL has supported making GraphQL queries via HTTP GET requests for several years. And many WordPress hosts already support HTTP caching of WPGraphQL requests made over GET requests.

The way Network cache works, is that there’s a caching layer between the client application requesting data, and the WordPress server itself.

The client application will send the GraphQL Query over HTTP GET. The first time the query is sent, this will be a “cache miss” and the request will make its way to the WordPress server for execution. WordPress will load up and process the request, executing all of the resolvers for the GraphQL query and a response will be returned.

The response will then be cached in a network cache layer (typically Varnish or similar). Whatever the layer is, if your host supports network caching, the request will be cached there.

Then, the next time the same query (and same variables) is executed, the response will be served from the cache instead of sending the request all the way to WordPress for execution.

Let’s take a look at this in action.

HTTP GET: Cache Miss

Below is a screenshot of an HTTP GET request, with the Chrome Developer tools open, highlighting the request in the “Network” tab. The first time the query is executed, it will be a Cache MISS. We can see this in the the response headers. The request took 304ms to respond.

HTTP GET: Cache Hit

Now, if we refresh the browser, the request will be returned from the network cache, and my WordPress server won’t be hit at all.

Here we can see that after refreshing multiple times, we’re getting the response in 53ms, and the response headers indicate the response is a cache hit.

We can keep refreshing, and keep getting responses from the cache.

If you had a client-side component on a page that was executing this query with HTTP GET, and the page received 10,000 visitors at the same time, 9,999 of those visitors would get a response from the network cache, instead of the WordPress server.

By switching your GraphQL client from using POST requests to using GET requests, you can start taking advantage of HTTP caching and your server will immediately have a reduced burden.

Cache Invalidation

Caching is great, but that’s only one part of the picture. The other part is Cache Invalidation, and this is where WPGraphQL Smart Cache really shines.

Stale Data

If all you do is switch from HTTP POST requests to HTTP GET requests, if your host supports network caching for GraphQL queries, you will benefit from faster responses and a reduced load on your WordPress server, but the cached responses will remain cached for a period of time, determined by the max-age headers of the response.

In this screenshot we see the “cache-control” header is set to 600 seconds. That means that for 10 minutes the response will be cached. Any requests for this query in the next 10 minutes will get a cached response, even if these posts were edited or new posts were published in those 10 minutes.

This isn’t ideal.

When building decoupled applications, we want the GraphQL response times to be fast, but we also want the data to be accurate.

Auto purging caches when data changes

WPGraphQL Smart Cache listens for publish, update and delete events in WordPress, and then emits a “graphql_purge” action to purge caches related to the object that was modified.

The eviction strategy is as follows:

  • publish: purge( ‘list:$type_being_published )
  • update: purge( $id_of_node_being_updated )
  • delete: purge( $id_of_node_being_deleted )

This is more nuanced than simply calling purge in response to actions like “save_post”.

We’ll discuss that nuance below.

Publish Events

There’s a lot of nuance around what is considered “Public” vs “Private” and WPGraphQL Smart Cache takes this into consideration.

Creating a new post isn’t automatically considered a “publish” event. New posts are created in many states, such as “draft” or “auto draft”, and draft posts aren’t considered public, and therefore wouldn’t be in responses of public queries that would be cached.

Transitioning a draft post to become a published post, however, would convert a post from a non-public state to a public state, and this would trigger WPGraphQL Smart Cache to call “purge( “list:post” )“.

If the post being published was a page, it would call “purge( “list:page” )” and if it were a custom post type it would call “purge( “list:$graphql_type_name_for_the_cpt” )“.

When it comes to other data, such as users and menus, there’s even more nuance.

Users in WordPress are not considered public entities by default.

If you create a user in WordPress, you must login to WordPress to know that user exists. However, once content is published with that user as the author, that user becomes a public entity, with an archive page, a REST API endpoint, etc.

WPGraphQL Smart Cache treats this scenario as a publish event. So, when a User becomes the author of their first publicly visible post, this would trigger “purge( “list:user” )“, evicting any queries that asked for a list of users.

Menus are similar, in that Menus that are not assigned to a Menu Location are considered non-public entities in WordPress. Once a Menu is assigned to a location, that would trigger “purge( “list:menu” )” and any queries that asked for a list of menus should be evicted.

Update Events

Update events are a bit more straightforward than publish events.

If an object is considered public (published post, user with published content, menu assigned to a location, etc), and that object is updated, it will trigger “purge( $id_of_object )“. This would evict any cached queries that had that “node” in its results.

Delete Events

Delete events are as nuanced as Publish events.

Simply listening to the “delete_post” hook isn’t enough. We need to know when posts transition from a public state to a non-public state to be considered a “delete” event.

If a GraphQL query returned a post or a list of posts, and one of those posts was converted from “publish” back to “draft”, that would be considered a “delete” event and any caches with that post would be evicted. This would trigger “purge( $post_id )“. Of course, if a published post was moved to the trash or force deleted, this would also trigger.

If a “draft” post is moved to the trash, this event is not triggered because it would be deleting something that’s not considered public, and therefore not in cached responses that would need purging.

Supported Hosts & Hosting Guide

In order for Network Cache invalidation to work, this requires some implementation details supported by the WordPress host.

When WPGraphQL returns a response, it contains an “X-GraphQL-Keys”.

This is a list of keys relevant to the query and the response. The list of keys contains information about what types of nodes were querying as a list as well as the individual node IDs.

When the host caches the response, it needs to be able to use these keys to “tag” the cached document.

Then, the host needs to be able to purge cached documents when that tag is purged.

WP Engine is the first host to fully support WPGraphQL Smart Cache. You can give it a try by signing up for a free sandbox account.

If you are a WordPress host that wants to support WPGraphQL Smart Cache, or you want to tell your host to support it, we have a hosting guide.

Object Cache

WPGraphQL provides support for object cache of GraphQL queries. This works similar to network cache, but instead of being cached at a level above WordPress, its cached within WordPress object cache layers.

This does require WordPress to load and retrieve the cache, so there’s more overhead than network cache, but it still reduces the overall processing required to return results for a GraphQL query.

If your WordPress host supports persistent object cache, such as memcached or similar, the cache will be stored there. If not, it will fall back to transients.

WPGraphQL Smart Cache does not enable Object Cache by default. To enable it, you need to visit the settings page under “GraphQL > Settings > Cache” and check the option to “Use Object Cache”.

With this enabled, GraphQL queries made using HTTP POST requests can benefit from caching. This can come in handy if your client application can’t use GET. For example, if you had developed a native iOS application, even if you wanted to switch to GET, the application can’t be changed until the user downloads an update.

From this settings page, you can also purge the GraphQL Object cache.

The invalidation of the object cache works the same as the invalidation for the network cache, but doesn’t require specific implementation details from your host.

When possible, we recommend using GET requests to take advantage of HTTP / network cache as it will lead to faster responses and greater processing reduction on your WordPress server.

Persisted Queries

Persisted Queries is a feature provided by WPGraphQL Smart Cache that is often used together with Network cache.

GraphQL documents can get quite long, so using GET requests for some queries isn’t realistic as there are query string length limitations.

Persisted Queries help avoid this problem.

With persisted queries, you can store GraphQL Query documents on the WordPress server, then make HTTP GET requests referencing the query by ID, instead of passing the full query string in the URL.

The Admin User Interface for Persisted Queries is hidden by default (we will likely be iterating how the UI in the admin looks and behaves, so don’t get married to it’s current state).

To show the UI, you can visit “GraphQL > Settings > Saved Queries” and check the option to “Display saved query documents in admin editor”.

This adds a “GraphQL Documents” menu item in your WordPress Dashboard.

From this User Interface, you can manage GraphQL Documents.

There’s a list view of the documents:

And an individual document editor where you an add the document, provide an optional description, assign aliases (ids), set the allow/deny rule, and customize the max-age header.

Once a document is saved, it can by queried by id.

For example, in the screenshot above, the ID of the document is: “b5f0fe422d78ffdfc7a43206693a8268d2fe368ec0f7d737e778efd5a3a7a08d”. That can be used in a GET request as the value of the “queryId” parameter:

In the screenshot we can see an HTTP GET request to the GraphQL endpoint using the “alias” as the value of the “queryId” parameter, and the results of the referenced Query Document being returned.

Automatic Persisted Queries (APQ)

Automatic Persisted Queries is a technique made popular by Apollo and is supported by WPGraphQL Smart Cache.

The way this works, is that the client application will try and use HTTP GET requests to fetch queries by ID (a hash of the query). The server responds with the results of the query if it can. If the ID is not recognized, the server returns a specific error, and the client follows-up with an HTTP POST request with the full query document and the query ID. The server then stores the GraphQL document and associates it with the ID, and future HTTP GET requests referencing the ID will execute the document.

This technique is implemented and active on WPGraphQL.com.

Allow / Deny Lists

One benefit of having GraphQL Documents saved on the server, is the ability to define allow and deny lists.

By default the WPGraphQL API allows arbitrary execution of queries. Sometimes you might want to disable that and only allow queries you’ve pre-defined.

To restrict the GraphQL endpoint to only execute allowed queries, you can visit “GraphQL > Settings ?> Saved Queries” and select the “Allow only specific queries” value for the “Allow/Deny Mode” setting.

With “allow” mode enabled, arbitrary queries will not be executed. Instead, an error will be returned.

One workflow teams can implement is as follows:

Front-end teams work on the application, writing all the queries they need for the application. Once the front-end team is ready, they generate a list of queries needed for the application and save them in the production environment as GraphQL Documents marked as “allowed”.

Then the production app can execute the queries against the server, while preventing arbitrary queries from being executed.

Conclusion

WPGraphQL Smart Cache is a new and exciting piece of the WPGraphQL ecosystem. We believe that the features provided by WPGraphQL Smart Cache will help teams scale their use of decoupled WordPress and unlock new possibilities for what can be built with this stack.

We’d love to hear what you build with WPGraphQL and WPGraphQL Smart Cache!

You can get the plugin here: https://wordpress.org/plugins/wpgraphql-smart-cache.

If you enjoy the plugin, please leave a review.

New Post Type and Taxonomy Registration options

WPGraphQL makes it easy to expose Custom Post Types and Taxonomies to the GraphQL Schema.

WPGraphQL v1.12.0 introduces new options for how your post types and taxonomies are added to the WPGraphQL Schema.

If you’re already familiar with how registering post types and taxonomies to GraphQL works, go ahead and skip to the new options or read the tutorial. But if you are new to it or want a refresher, keep reading.

Registering Post Types and Taxonomies to show in the WPGraphQL Schema

When registering a custom post type or taxonomy to WPGraphQL, there are 3 required arguments to add:

  • show_in_graphql | boolean | (required) : Whether to expose this post type or taxonomy to the GraphQL API.
  • graphql_single_name | string | (required): The GraphQL Type name that will represent nodes of this post type or taxonomy.
  • graphql_plural_name | string | (required) : The plural name of the GraphQL Type for use in connection field names.

With these 3 properties defined in the registration of a post type or taxonomy, a lot of things are added to the GraphQL Schema.

Let’s dive in!

For this example, we will start with a “testimonial” post type.

We could register the post type like so:

add_action( 'init', function() { 

  register_post_type( 'testimonial', [
    'public' => true,
    'label' => __( 'Testimonials', 'wp-graphql' ),
    'show_in_graphql' => true, # tells the post type to show in the GraphQL Schema
    'graphql_single_name' => 'Testimonial', # determines the GraphQL Type name for nodes of this post type
    'graphql_plural_name' => 'Testimonials', # determines the field name for connections to nodes of this post type
  ] );

} );

By adding these 3 lines of code (show_in_graphql, graphql_single_name, graphql_plural_name) to our post type registration, we are now able to query and mutate posts of the “testimonial” post type using GraphQL.

Let’s take a look at how these 3 lines affected the WPGraphQL API.

Schema

If we were to open the GraphiQL IDE documentation explorer and search our GraphQL Schema for “testimonial” (before setting the post type to show_in_graphql) we would see no results, like so:

Screenshot of GraphiQL Schema Docs with no results for “testimonial” search

If we search for “testimonial” (after setting the post type to “show_in_graphql”) we will see many different Types and Fields in the Schema.

Screenshot of GraphiQL Schema Docs with default results for “testimonial” search

Let’s break down exactly what was added to the GraphQL Schema.

Types & Fields

In the screenshot above, the gold-colored words represent GraphQL Types, and the blue-colored words represent fields.

By registering the post type to show_in_graphql, the schema now has 12 new GraphQL Types and 10 new fields, all for interacting with posts of the “testimonial” post type. (these numbers might differ in your system based on different conditions, but the point is, you should have a lot of new Types and Fields with minimal effort).

The GraphQL Types define the shape of objects in the graph, and the fields are the entry points into the graph for accessing data.

Let’s take a look more specifically at the Queries and Mutations provided by exposing this post type to the GraphQL Schema.

I’m not going to break down every single Type and Field that was added to the schema, but we’ll take a look at some of the main ones.

Queries

If we take a look at the fields that were added to the RootQuery type, we can see a few different ways we can now query for content in our “testimonial” post type.

  • RootQuery.testimonial: This allows for individual Testimonial nodes to be queried by id.
  • RootQuery.testimonialBy (deprecated): This is a deprecated version of the previous field.
  • RootQuery.testimonials: This allows for lists (called connections in GraphQL) of testimonials to be queried.

Mutations

If we look at the fields added to the RootMutation, we will see the following mutations:

  • RootMutation.createTestimonial
  • RootMutation.updateTestimonial
  • RootMutation.deleteTestimonial

These mutations allow for content of the testimonial post type to be created, updated and deleted via GraphQL mutations.

GraphQL Mutations respect the read and write privileges defined for each post type, so only authenticated users with proper capabilities can successfully execute these mutations.

New post type and taxonomy registration options

Now that we understand how registering post types and taxonomies to WPGraphQL works, we’ll take a look at the new options introduced in WPGraphQL v1.12.0 and see how they work.

Below are the new arguments that can be passed to register_post_type and register_taxonomy to provide further customization of how it will be represented in the GraphQL Schema:

  • graphql_kind: Allows the type representing the post type to be added to the graph as an object type, interface type or union type. Possible values are ‘object’, ‘interface’ or ‘union’. Default is ‘object’.
  • graphql_resolve_type: The callback used to resolve the type. Only used if “graphql_kind” is set to “union” or “interface”.
  • graphql_interfaces: List of Interface names the type should implement. These will be applied in addition to default interfaces such as “Node”.
  • graphql_exclude_interfaces: List of Interface names the type should not implement. This is applied after default and custom interfaces are added, so this can remove default interfaces.
    • Note: Interfaces applied by other interfaces will not be excluded unless that interface is also excluded. For example a post type that supports “thumbnail” will have NodeWithFeaturedImage interface applied, which also applies the Node interface. Excluding “Node” interface will not work unless “NodeWithFeaturedImage” was also excluded.
  • graphql_fields: Array of fields to add to the Type. Applied if “graphql_kind” is “interface” or “object”.
  • graphql_exclude_fields: Array of fields names to exclude from the type. Applies if “graphql_kind” is “interface” or “object”.
    • Note: any fields added by an interface will not be excluded with this option.
  • graphql_connections: Array of connection configs to register to the type. Only applies if the “graphql_kind” is “object” or “interface”.
  • graphql_exclude_connections: Array of connection field names to exclude from the type. Only connections defined on the type will be excluded. Connections defined on an Interface that is applied to the Type remain even if “excluded” in this list.
  • graphql_union_types: Array of possible types the union can resolve to. Only used if “graphql_kind” is set to “union”.
  • graphql_register_root_field: Whether to register a field to the RootQuery to query a single node of this type. Default true.
  • graphql_register_root_connection: Whether to register a connection to the RootQuery to query multiple nodes of this type. Default true.
  • graphql_exclude_mutations: Array of mutations to prevent from being registered. Possible values are “create”, “update”, “delete”.

For more information, check out the official documentation for adding Custom Post Types and Custom Taxonomies to the WPGraphQL Schema.

And for a more in-depth look at these options, check out the tutorial where we walk through multiple scenarios for managing “food” in the CMS and querying via WPGraphQL.