Allowing WPGraphQL to show unpublished authors in User Queries

Objective

The goal of this article is to make a non-authenticated GraphQL query for a User with no published content and to get the User returned in the response.

Like the following:

{
  users(where:{login:"UnpublishedAuthor"}) {
    nodes {
      id
      name
    }
  }
}
Screenshot of a Query for an unpublished author and the results showing the Unpublished Author data.

NOTE: To test these queries you will want to use a GraphQL client that is not Authenticated, such as GraphQL Playground or Altair. WPGraphiQL, the GraphiQL IDE built-in to WPGraphQL in the WordPress dashboard currently executes GraphQL queries as your authenticated user.

The Problem

WPGraphQL respects WordPress core access control rights. Meaning that data which is exposed publicly by WordPress core is exposed publicly in WPGraphQL, but data that is exposed only in the WordPress Dashboard is restricted by WPGraphQL to GraphQL requests made by authenticated users with proper capabilities to see the data.

In WordPress, users that have not published content are not public entities and WPGraphQL respects this.

This means by default, the same query as above would return empty results, because the “UnpublishedAuthor” user is not allowed to be seen by a public, non-authenticated GraphQL request.

Screenshot of a GraphQL Query or a user filtered by user login, showing no results.

In some cases, you might decide you want to show Users that don’t have published content in the results of your WPGraphQL Queries.

The Solution

The below snippets should help accomplish this.

Adjust the underlying WP_User_Query

When using WPGraphQL to query a connection (a list) of users, WPGraphQL sets an argument of 'has_published_posts' => true for the underlying WP_User_Query, meaning that the SQL query for a list of users will reduce the results to users that have published posts.

To adjust this, we can use the `graphql_connection_query_args` like so:

add_filter( 'graphql_connection_query_args', function( $query_args, $connection_resolver ) {

  if ( $connection_resolver instanceof \WPGraphQL\Data\Connection\UserConnectionResolver ) {
    unset( $query_args['has_published_posts'] );
  }

  return $query_args;

}, 10, 2 );

Filter the User Model to make all Users public

WPGraphQL has a Model Layer that centralizes the logic to determine if any given object, or fields of the object, should be allowed to be seen by the user requesting data.

The User Model prevents unpublished users from being seen by non-authenticated WPGraphQL requests.

To lift this restriction, we can use the following filter:

add_filter( 'graphql_object_visibility', function( $visibility, $model_name, $data, $owner, $current_user ) {

  // only apply our adjustments to the UserObject Model
  if ( 'UserObject' === $model_name ) {
    $visibility = 'public';
  }

  return $visibility;

}, 10, 5 );

Testing our Changes

Now that we’ve adjusted WPGraphQL to show all users to public GraphQL requests, we can use GraphiQL to test.

For the sake of testing, I created a new User with the name of “Unpublished Author” and will make a GraphQL Query for users, like so:

{
  users {
    nodes {
      id
      name
    }
  }
}

And with the above snippets in place, I’m now able to see the UnpublishedAuthor in my GraphQL query results.

Screenshot of a Query for an unpublished author and the results showing the Unpublished Author data.

Announcing WPGraphQL v1.0

I’m so excited to announce WPGraphQL 1.0!

If you’ve been using WPGraphQL for your side projects, but are waiting for the “stable” version, then this is it! You can use WPGraphQL in production with the confidence that it is secure, well documented, and supported full time and long term.

If you’re new to the WPGraphQL community, you may ask yourself, “But WordPress already has the REST API. What’s different about using WPGraphQL?”

Well, when you use WPGraphQL to turn your WordPress site into a GraphQL server, multiple resources can be fetched at the same time. And by design you get back the exact data you asked for, nothing more, nothing less. In addition to being easier to work with compared to the REST API, data loading with GraphQL is faster and more efficient. (Read more: “WPGraphQL vs. WP REST API“)

If you are upgrading to 1.0 or another version, see the release notes for information on breaking changes and preparing your site. If you are new, start at the homepage and follow our step-by-step docs.

In this post I want to talk about the journey to 1.0, why it took so long to get to 1.0, and a look at what’s next for WPGraphQL. But before diving into that, I want to also announce some other fun stuff that has also come to life along with this release.

  • WPGraphQL is now available on the WordPress.org repository
  • New WPGraphQL.com website
  • WPGraphQL Swag available on the Gatsby Swag Store

WPGraphQL on WordPress.org

The WordPress.org plugin repository has long been the default distribution mechanism for WordPress plugins. Prior to today, developers had to install WPGraphQL using Composer and installing from packagist, or by downloading from Github.

Now, users can install WPGraphQL directly from WordPress.org’s plugin directory.

We believe that distributing WPGraphQL on the WordPress.org plugin repository should make it easier for users to find and install and keep updated.

New WPGraphQL.com website

WPGraphQL.com has been re-built from the ground up.

Before today, WPGraphQL had 2 primary domains:

  • WPGraphQL.com, a “traditional” WordPress site using WordPress as the CMS and the theme layer
  • docs.wpgraphql.com, a Gatsby site using Markdown files in a Github repository for the content.

Now, instead of splitting some content in Markdown files on Github and some content in WordPress, WPGraphQL.com now uses WordPress as the CMS for all content, and and has a Gatsby front-end powered by the new Gatsby Source WordPress plugin, WPGatsby and of course WPGraphQL.

We’ll be releasing tutorials and videos in the coming weeks and months walking through how various parts of the site were created to provide inspiration for you to build great things with this stack as well, but for now you can draw inspiration from the repository this site is built from: https://github.com/wp-graphql/wpgraphql.com

WPGraphQL Swag

Now, possibly the most exciting thing about this whole announcement, is the fact that you can now get WPGraphQL Swag from the Gatsby swag store.

The Journey to 1.0

WPGraphQL was started in November 2016 (with no commit message ???? ) and has come a long way in that time.

Screenshot of the “Initial Commit” in Github

By the end of 2017 the codebase was pretty robust and provided a Schema to interact with many parts of WordPress. Posts, Pages, Custom Post Types, Tags, Categories, Custom Taxonomies, Comments and Nav Menus.

Qz.com was the first site to go to production with WPGraphQL, even before I was using it in production! A few years later and they are still running on WPGraphQL! You can learn more about the early days of their stack from this presentation from December 2017.

In early 2018, my team took WPGraphQL into production on a network of 54 large WordPress sites. WPGraphQL was used as a syndication engine for our network of newspapers. Unlike most GraphQL users, we didn’t initially have a JavaScript or native mobile UI that was using GraphQL, we had PHP WordPress servers syndicating content and GraphQL solved a lot of pain points we had been facing when using REST.

By the end of 2018, more than 100 sites were using WPGraphQL in some way.

Now, WPGraphQL seems to be running all over the place!

This is a non-comprehensive list of sites using WPGraphQL in production in some way:

According to Packagist.org as of June 30, 2020 there were nearly 50,000 installs of WPGraphQL in the wild, and as of November 2020 Packagist reports 71,573 installs.

In 2019 Credit Karma paid for a security audit on the plugin and all reported issues were resolved.

It’s safe to say, WPGraphQL is production ready!

So, why the wait for 1.0?

So, if WPGraphQL has been production ready for such a long time, why is is still not 1.0?

Breaking Changes & WordPress

The WordPress ecosystem has a strong commitment to backward compatibility. This means that once functionality is introduced to a WordPress plugin, it often sticks around forever.

With that in mind, before tagging WPGraphQL 1.0, I wanted so badly to be at a place with the codebase & Schema that I could no longer predict anymore breaking changes.

I wanted the GraphQL Schema to be “perfect” before tagging it 1.0, because I knew that whatever would be in the WPGraphQL Schema at 1.0 would need to remain in the Schema forever. The WordPress community is used to things working the same way “forever” and I wanted to play along.

Breaking Changes & WPGraphQL

The reality is that for software to get better, sometimes it needs to make breaking changes.

WPGraphQL will never be perfect, and it will need to occasionally break to make things better.

For example, when the GraphQL Specification updates to include Input Unions or Interfaces that can implement other Interfaces, WPGraphQL will need to break to keep up with the GraphQL Specification.

There would be no way to keep GraphQL as we know it today and take advantage of tomorrow’s GraphQL Spec without breaking.

WPGraphQL turning 1.0 isn’t a statement that there will never be breaking changes, instead it’s a statement of stability and long term support.

So, how will breaking changes be handled in a post-1.0 world?

Semantic Versioning

WPGraphQL has been following Semver practices for a few years. We will continue to follow Semver and let version numbers communicate meaning. The summary of Semver versioning is as follows:

  • MAJOR version when you make incompatible API changes,
  • MINOR version when you add functionality in a backwards compatible manner, and
  • PATCH version when you make backwards compatible bug fixes.

You can read more about the details of Semver at semver.org

Seamless Upgrade Paths

When WPGraphQL needs to make breaking changes to the Schema, there are sometimes ways to make the changes and allow for seamless upgrade paths.

What that means, is that WPGraphQL can introduce a new feature while keeping the old feature. For example, let’s say the following (hypothetical) query were in the Schema and the author field returned the name of the Author:

{
  book {
    author
  }
}

Let’s say we realized that the Author should actually be a one-to-one connection to an object with edge space for relational context. We would want to change the schema to allow for the following query instead.

{
  book {
     author {
         node {
             name
          }
     }
  }
}

If WPGraphQL made this change, any consumer application would break.

So, what we can do is introduce the new feature like so:

{
   book {
     author
     authoredBy {
        node {
            name
        }
     }
   }
}

We could introduce the new feature as a new field name authoredBy which returns the data in the shape we want.

The end goal, however, is to have the author field return the author.node.name shape.

So, with both fields (author and authoredBy) co-existing on the server, consumer applications could make use of GraphQL Aliases and update their code from:

{
  book {
     author
  }
}

to:

{
   book {
      author: authoredBy { # Consumer uses an alias to prepare for the upcoming change to the author field
          node {
             name
          }
      }
   }
}

Once consumers have updated their code, the server could then update once more to flip the old field name to the new shape. So, the next release would have the following possible:

{
   book {
      author {
         node {
             name
          }
      }
      authoredBy {
        node {
            name
        }
     }
   }
}

So now, the old field author no longer returns the name directly, it now returns the new nested node/name shape. The client is using an alias to query the authoredBy field, so now the consumer should update their code from:

{
   book {
      author: authoredBy { # Consumer uses an alias to prepare for the upcoming change to the author field
          node {
             name
          }
      }
   }
}

to:

{
   book {
      author
          node {
             name
          }
      }
   }
}

Now, the consumer application is using the new shape of data and the old author field, and one more release would be able to remove the temporary authoredBy field that was used for seamless upgrades.

Query posts based on Advanced Custom Field values by Registering a Custom “where” Argument

If you manage custom fields in WordPress using Advanced Custom Fields, and you want to use WPGraphQL to get a list of posts filtering by the ACF field value, you’ll need to implement a few things in your themes functions.php.

Summary:

  • Register a new “where” argument to the WPGraphQL Posts connection
  • Filter the Posts connection resolver to account for this new argument

Register the new “where” argument:

First you need to create a add_action on graphql_register_types that will look something like the following code snippet. Here we register a field on the RootQueryToMyCustomPostTypeConnectionWhereArgs where you can define MyCustomPostType as your post type. The type we register will be an ID (This can also be of type Boolean, Float, Integer, orString) for my case I wanted to get only posts that where connected to an other post via the ACF post object field (the field was set to return only the Post ID).

Filter the connection resolver

add_action('graphql_register_types', function () {

    $customposttype_graphql_single_name = "MyCustomPostType";

    register_graphql_field('RootQueryTo' . $customposttype_graphql_single_name . 'ConnectionWhereArgs', 'postObjectId', [
        'type' => 'ID',
        'description' => __('The ID of the post object to filter by', 'your-textdomain'),
    ]);
});

Next we have to create an add_filter to graphql_post_object_connection_query_args. If you are familiar with WordPress loops via WP_Query, here we set the $query_args like we would do on any other loop, but we check for your custom where:.

add_filter('graphql_post_object_connection_query_args', function ($query_args, $source, $args, $context, $info) {

    $post_object_id = $args['where']['postObjectId'];

    if (isset($post_object_id)) {
        $query_args['meta_query'] = [
            [
                'key' => 'myCustomField',
                'value' => $post_object_id,
                'compare' => '='
            ]
        ];
    }

    return $query_args;
}, 10, 5);

The key will be the name of the field, the value will be the value you will give the postObjectId: "123" in your query, speaking of the query that will ook something like

query GetMyCustomPostType {
  MyCustomPostType(where: {postObjectId: "123"}) {
    nodes {
      title
    }
  }
}

This will get all your MyCustomPostType where myCustomField = 123

Registering Types and Fields: Pair Programming with Jacob Arriola

On Thursday, March 26, 2020 I pair programmed with Jacob Arriola, Senior Web Engineer at Zeek Interactive.

Zeek Interactive is a California-based software agency specializing in WordPress and Apple iOS. They recently moved their own marketing website from traditional WordPress to Headless WordPress with WPGraphQL as the API and Gatsby as the front-end.

With the recent move of their site to Gatsby, and more Gatsby + WordPress projects in the pipeline, Jacob wanted to get more familiar with extending the WPGraphQL Schema.

In this video, we walk through registering custom Types and Fields to the WPGraphQL Schema. We use the popular WordPress SEO by Yoast plugin as an example of custom data we’d like to expose to the WPGraphQL Schema.

NOTE: There is an existing WPGraphQL + WordPress SEO by Yoast extension here: https://github.com/ashhitch/wp-graphql-yoast-seo

Recording of the Livestream

Recording of the LiveStream Pair Programming with Jason Bahl and Jacob Arriola

Forward and Backward Pagination with WPGraphQL

WPGraphQL makes use of cursor-based pagination, inspired by the Relay specification for GraphQL Connections. If you’re not familiar with cursor-based pagination it can be confusing to understand and implement in your applications.

In this post, we’ll compare cursor-based pagination to page-based pagination, and will look at how to implement forward and backward pagination in a React application using Apollo Client.

Before we dive into cursor-based pagination, let’s first look at one of the most common pagination techniques: Page-based pagination.

Page Based Pagination

Many applications you are likely familiar with paginate data using pages. For example, if you visit Google on your desktop and search for something and scroll down to the bottom of the page, you will see page numbers allowing you to paginate through the data.

Screenshot of Google’s pagination UI

WordPress also has page-based pagination mechanisms built-in. For example, within the WordPress dashboard your Posts are paginated:

Screenshot of the WordPress dashboard pagination UI

And many WordPress themes, such as the default TwentyTwenty theme feature page-based pagination:

Screenshot of the TwentyTwenty theme pagination UI

How Page-Based Pagination Works

Page-based pagination works by calculating the total number of records matching a query and dividing the total by the number of results requested for each page. Requesting a specific page results in a database request that skips the number of records totaling the number of records per page multiplied by the number of pages to skip. For example, visiting page 9 on a site showing 10 records per page would ask the database to skip 90 records, and show records 91-100.

Performance Problems with Page-Based Pagination

If you’ve ever worked with on a WordPress site with a lot of records, you’ve likely run into performance issues and looked into how to make WordPress more performant. One of the first recommendations you’re likely to come across, is to not ask WordPress to calculate the total number of records. For example, the 10up Engineering Best Practices first recommendation is to set the WP_Query argument no_found_rows to true, which asks WordPress to not calculate the total records.

As the total number of records grow, the longer it takes for your database to execute this calculation. As you continue to publish posts, your site will continue to get slower.

In addition to calculating the total records, paginated requests also ask the database to skip a certain number of records, which also adds to the weight of the query. In our example above, visiting page 9 would ask the database to skip 90 records. In order for a database to skip these records, it has to first find them, which adds to the overall execution. If you were to ask for page 500, on a site paginated by 100 items per page, you would be asking the database to skip 50,000 records. This can take a while to execute. And while your everyday user might not want to visit page 500, some will. And search engines and crawlers will as well, and that can take a toll on your servers.

The (lack of) Value of Page Numbers in User Interfaces

As a user, what value do page numbers provide in terms of user experience?

For real. Think about it.

If you are on my blog, what does “page 5” mean to you?

What does “page 5” mean to you on a Google search results?

Sure, it means you will get items “50-60”, but what does that actually mean to you as a user?

You have no realistic expectation for what will be on that page. Page 5 on my blog might be posts from last week, if I blogged regularly, but Page 5 also might be blog posts from 2004. You, as the user don’t know what to expect when you click a page number. You’re just playing a guessing game.

It’s likely that if you clicked page 5 on my blog, you are looking for older content. Instead of playing a guessing game clicking arbitrary page numbers, it would make more sense and provide more value to the user if the UI provided a way to filter by date ranges. This way, if you’re looking for posts from yesterday, or from 2004, you could target that specifically, instead of arbitrarily clicking page numbers and hoping you find something relevant to what you’re looking for.

Inconsistent data in page-based UIs

Page based UIs aren’t consistent in the content they provide.

For example, if I have 20 blog posts, separated in 2 pages, page 1 would include posts 20-11 (most recently published posts), and page 2 would include posts 1-10. As soon as I publish another article, page 1 now would include items 21-12, page 2 would include items 2-11, and we’d have a page 3 with item 1, the oldest post.

Each time a new blog post is published, the content of each paginated archive page changes. What was on page 5 today, won’t be the same next time content is published.

This further promotes the fact that the value provided to users in page numbers is confusing. If the content on the pages remained constant, at least the user could find the content they were looking for by always visiting page 5, but that’s not the case. Page 5 might look completely different next time you click on it.

Cursor-Based Pagination

Many modern applications you’re likely familiar with use cursor-based pagination. Instead of dividing the total records into chunks and skipping records to paginate, cursor-based pagination loads the initial set of records and provides a cursor, or a reference point, for the next request to use to ask for the next set of records.

Some examples of this type of pagination in action would be loading more tweets as you scroll on Twitter, loading more posts as you scroll on Facebook, or loading more Slack messages as you scroll in a channel.

Animated GIF demonstrating scrolling in Slack to load more messages

As you scroll up or down within a Slack channel, the next set of messages are loaded into view (often so fast that you don’t even notice). You don’t arbitrarily click page numbers to go backward or forward in the channel and view the next set of messages. When you first enter a channel, Slack loads an initial list of messages and as you scroll, it uses a cursor (a reference point) to ask the server for the next set of messages.

Michael Hahn, a Software Engineer at Slack wrote about how Slack uses cursor pagination.

In some cases, such as Slack channels, infinite scrolling for pagination can enhance the user experience. But in some cases, it can harm the user experience. The good news is that cursor-based pagination isn’t limited to being implemented via infinite scroll. You can implement cursor pagination using Next / Previous links, or a “Load More” link.

Google, for example, when used on mobile devices uses a “More results” button. This avoids subjecting users to the guessing game that page numbers lead to, and also avoids some of the downsides of infinite scrolling.

Screenshot of Google search results with a “More results” button, as seen on a mobile device

Performance of Cursor Based Pagination

Cursor pagination works by using the reference, the cursor, to point to a specific place in the dataset, and move forward or backward from that point. Page-based pagination needs to skip x amount of records, where cursor pagination can go directly to a point and start there. As you page through records with page-based pagination your queries get slower and slower, and it’s further impacted by the size of the dataset. With cursor pagination, the size of your dataset doesn’t affect performance. You are going to a specific point in the dataset and returning x number of records before or after that point. Going 500 pages deep will get quite slow on page-based, but will remain equally performant with cursor-based pagination.

No Skipped Data

Let’s say you visit your Facebook newsfeed. You immediately see 5 posts from your network of family and friends. Let’s say the data looks something like the following:

  1. COVID-19 update from Tim
  2. Meme from Jen
  3. Embarrassing Photo 1 from Mick
  4. Embarrassing Photo 2 from Mick
  5. Dog photo from Maddie
  6. Quarantine update from Stephanie
  7. Inspirational quote from Dave
  8. Breaking Bad meme from Eric
  9. Quarantine update from your local newspaper
  10. Work from Home tips from Chris

In page-based pagination, the data would look like so:

Page 1Page 2
COVID-19 update from TimQuarantine update from Stephanie
Meme from JenInspirational quote from Dave
Embarrassing Photo 1 from MickBreaking Bad meme from Eric
Embarrassing Photo 2 from MickQuarantine update from your local newspaper
Dog photo from MaddieWork from Home tips from Chris

Let’s say Mick wakes up and realizes he shouldn’t have posted the embarrassing pics. He deletes the pics at the same time you’re looking at Page 1 of the posts.

When you scroll to look at more posts, the overall dataset now looks like the following:

Page 1Page 2
COVID-19 update from TimBreaking Bad meme from Eric
Meme from JenQuarantine update from your local newspaper
Dog photo from MaddieWork from Home tips from Chris
Quarantine update from Stephanie5 Keto Recipes from Barb
Inspirational quote from Dave“Tiger King” trailer from Cassie

Since the two embarrassing photos were deleted, page 2 now looks different. Both “Quarantine update from Stephanie” and “Inspirational quote from Dave” are not on Page 2 anymore. They slid up to page 1. But the user already loaded page 1 which included the now deleted pice. So, when you scroll, and page 2 loads, these two posts won’t be included in your feed!

With page-based pagination, you would miss out on some content and not have any way to know that you missed it!

And Dave and Stephanie will be sad that you didn’t like their posts.

This happens because the underlying SQL query looks something like this:

SELECT * from wp_posts OFFSET 0, LIMIT 5 // Page 1, first 5 records
SELECT * from wp_posts OFFSET 5, LIMIT 5 // Page 2, skips 5 records regardless

With cursor pagination, a pointer is sent back and used to query the next set of data. In this case, it might be a timestamp. So, when you scroll to load more posts, with cursor pagination you would get all items published after the last cursor. So, “Quarantine update from Stephanie” and “Inspirational quote from Dave” would be included because they were posted after the timestamp of the embarrassing photos that have been deleted. The cursor goes to a specific point in the dataset, and asks for the next set of records after that point.

The underlying SQL query for cursor pagination looks something like this:

SELECT * from wp_posts WHERE cursor > wp_posts.post_date ORDER BY wp_posts.post_date LIMIT 5

So, instead of skipping 5 records, we just get the next set of posts based on publish date (or whatever the query is being ordered by). This ensures that the user is getting the proper data, even if there have been changes to the existing dataset.

Cursor Pagination with WPGraphQL

Below is an example of forward and backward pagination implemented in a React + Apollo application.

(If your browser isn’t loading this example application below, click here to open it in a new window)

In this example, we’ll focus specifically on the list.js file.

Paginated Query

One of the first things you will see is a paginated query.

query GET_PAGINATED_POSTS(
    $first: Int
    $last: Int
    $after: String
    $before: String
  ) {
    posts(first: $first, last: $last, after: $after, before: $before) {
      pageInfo {
        hasNextPage
        hasPreviousPage
        startCursor
        endCursor
      }
      edges {
        cursor
        node {
          id
          postId
          title
        }
      }
    }
  }

In this query, we define the variables first, last, after and before. These are options that can be passed to the query to affect the behavior. For forward pagination first and after are used, and for backward pagination last and before are used.

In addition to asking for a list of posts, we also ask for pageInfo about the query. This is information that informs the client whether there are more records or not, and how to fetch them. If hasNextPage is true, we know we can paginate forward. If hasPreviousPage is true, we know we can paginate backwards.

Our PostList component makes use of the Apollo useQuery hook to make an initial query with the variables: { first: 10, after: null, last: null, before: null }. This will ask WPGraphQL for the first 10 posts, which will be the 10 most recently published posts.

When the data is returned, we map over the posts and return them and render the Post titles as unordered list items.

Next / Previous buttons

Since there are more than 10 posts, the pageInfo.hasNextPage value will be true, and when this value is true, we know we can safely show our Next button. Since this is the first page of data, there are no posts more recent, so pageInfo.hasPreviousPage will be false. Since this is false, we will not load the Previous Button.

The initial load of the application is a list of 10 posts with a Next button.

Screenshot of the example application’s initial loaded state

When the “Next” button is clicked, it makes use of the Apollo fetchMore method, and re-queries the same query, but changing the variables to: { first: 10, after: pageInfo.endCursor, last: null, before: null }. These variables tell WPGraphQL we want the first 10 posts after the endCursor, which is a reference to the last item in the list on the first page. When those posts are returned, we make use of Apollo’s updateQuery method to replace the Post list with the new list.

So now, after clicking “Next”, we have a new list of posts displayed, and both a “Previous” and “Next” button.

Screenshot of the example application after clicking the “Next” button

Both the Previous and Next buttons are displayed, because the values for pageInfo.hasNextPage and pageInfo.hasPreviousPage were both true. This information from the query tells the client that there are posts on either side, so we can paginate forward or backward.

If we click “Next” a few more times, we will reach the end of the dataset, and pageInfo.hasNextPage will be false, and we will no longer want to show the “Next” button.

Screenshot of the example application at the end of the dataset

When the “Previous” button is clicked, it makes use of the Apollo fetchMore method, and re-queries the same query, but changing the variables to: { first: null, after: null, last: 10, before: pageInfo.startCursor }. These variables tell WPGraphQL we want the last 10 posts before the startCursor, which is a reference to the first item in the list on the page. This allows us to paginate backward and get the previous items. When those previous posts are returned, we make use of Apollo’s updateQuery method to replace the Post list with the new list, and we’re back to showing both “Previous” and “Next” buttons again, until you click previous enough times to be back at the beginning of the dataset.

Summary

In this post, I compared page-based pagination and cursor-based pagination. We then looked at an example application built with React and Apollo querying the WPGraphQL.com GraphQL API and implementing forward and backward pagination.

I hope this article helps inspire you to use WPGraphQL in fun ways as we build the future of the web together!

WPGraphQL v0.8.0

Release Notes

TL;DR

This release focuses on adjusting how Nodes are resolved to prevent errors in cases where the nodes are determined to be considered private and non-viewable by the requesting user. (#1138)

More details below:

Schema Breaking Changes

Schema Breaking changes are changes to the shape of the Schema that would require clients interacting with WPGraphQL to update to remain compatible.

  • n/a: The shape of the Schema remains unchanged in this release. Clients shouldn’t need to adjust their queries/mutations to remain compatible with this release.

Internal Breaking Changes

Internal Breaking Changes are changes to internals that might require plugins that extend WPGraphQL to change in order to remain compatible.

  • BREAKING: There are changes to the AbstractConnectionResolver class that affect how connections are resolved. If you are a plugin author extending this class, you will need to update your classes that extend this.
  • BREAKING: Refactored ContentTypeConnectionResolver, TaxonomyConnectionResolver and UserRoleConnectionResolver to not extend the AbstractConnectionResolver class, but instead make use of the Relay::connectionFromArray() method
  • BREAKING: – Update Loaders (CommentLoader, MenuItemLoader, PostObjectLoader, TermObjectLoader, UserLoader) to return an array of resolved nodes (Models) instead of an array of Deferreds.
  • If your plugin extends or calls these classes, you may need to update your code. The loaders now return an array of Nodes (Models) instead of an array of Deferreds.

New Features

  • Accessibility improvements for the documentation. (See: #1049, #1150) Thanks @jacobarriola!
  • Remove resolveNode config arg from most connections registered by the core plugin as the new method for resolving connections doesn’t wait until the last second to resolve nodes
  • For example: https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-a04db7b019ce5a16e141cd35799a0718L19-L21, https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-0b5f575771fc27faf7455a8fa0a05d93L79-L81

Release Summary

WPGraphQL makes use of a concept called (Deferred Resolution)[https://github.com/wp-graphql/wp-graphql/pull/722#issue-261315185], to ensure that database queries are executed as efficiently as possible.

WPGraphQL was deferring the resolution of nodes too late, causing errors when Nodes were determined to be private after being loaded.

Take the following query for example :

{
  posts {
     nodes {
         id
         databaseId
         title
     }
  }
}

This query might return a payload like so:

{
  "data": {
    "posts": {
      "nodes": [
        {
          "id": "cG9zdDoyNDI=",
          "databaseId": 242,
          "title": "Test Post"
        },
        {
          "id": "cG9zdDox",
          "databaseId": 1,
          "title": "Hello world!"
        }
      ]
    }
  },
}

Looks great! Just what we’d expect (assuming the site had only 2 posts).

Well, let’s say we had a membership plugin installed (or something similar) that used meta to determine whether a Post should be considered private or not. And let’s say that Post 242 was set to private and should not be returned to a public user.

Because of how the Deferred resolution was working (before this release), the Post would have been resolved too late to be stripped out of the results, and would return a null within the list of Post nodes, and would include an error like so:

{
  "errors": [
    {
      "debugMessage": "Cannot return null for non-nullable field Post.id",
      ...
    }
  ],
  "data": {
    "posts": {
      "nodes": [
        null,
        {
          "id": "cG9zdDox",
          "databaseId": 1,
          "title": "Hello world!"
        }
      ]
    }
  },
}

This behavior is problematic.

First, it throws errors, when there really isn’t an error. Nothing has actually gone wrong. The user is asking for Posts, and GraphQL should be able to return posts without error.

If a Post is private, it shouldn’t be exposed at all. It should be as if it doesn’t even exist in the system. Be returning a null value, we are exposing that something is there behind the scenes.

The correct behavior should be to return a list of Posts, and no errors returned. If a Post is private, it should simply not be included in the list at all.

This release fixes this issue by changing how the Deferred resolution of nodes happens.

Given the query above, resolution used to work like so:

  • Posts: Use WP_Query to get a list of Post IDs. Pass those IDs to the next level of the Resolve Tree
  • Nodes: Use the ID of each node to load the Post using a Batch resolver, pass the Post through the Model Layer to determine if it’s public or private, then either return the Post or return a null value

Because of the late resolution of the Node, this was causing the Cannot return null for non-nullable field Post.id error. There’s no way to strip a private node out of the list of returned nodes if we’re resolving nodes at the last possible tree in the Graph.

This pull request changes the behavior to resolve the nodes earlier.

Given the query above, resolution now works like so:

  • Posts: Use WP_Query to get a list of Post IDs. Pass those IDs to a Deferred Resolver. The Deferred Resolver will make a batch request and load all Nodes, passed through the Model Layer. Nodes that are null will be filtered out now. A list of resolved nodes will be passed to the next level of the Resolve Tree:
  • Nodes: Return the nodes that are passed down. Private nodes will not be passed to this level, so no errors about Cannot return null for non-nullable field Post.id will be returned.

To accomplish this, some changes to the ConnectionResolver classes were made.

Now, Nodes are resolved a step earlier, and the resolved nodes are now passed from the Connection down to Edges/Nodes.

Edges/Nodes now have the full nodes as context in their resolvers, instead of just an ID.

This can be HUGE when needing to add edge data to connections, where before an entity ID was the only context provided, and that can be too little information to be useful.

You can read more about a concrete case where the functionality was problematic, and how this release fixes it here: https://github.com/wp-graphql/wp-graphql/issues/1138#issuecomment-580269285

Changes to the Abstract Connection Resolver

Below is a list of changes the AbstractConnectionResolver Class. If your Plugin extends this class, the below information should help with upgrading.

  • AbstractConnectionResolver adds the following breaking changes:
  • abstract public function get_loader_name()
    • This method was added to tell the connection resolver what Loader to use to load nodes using Deferred resolution. In order to extend the AbstractConnectionResolver, a Loader will also need to be created. You can see the existing Loaders here.
    • see: https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-015d5cec0dcc9f802dcfa99bc136f478R252-R260
  • abstract public function get_ids()
    • replaces previous get_items() method
    • This method was added as a way to tell the resolver what IDs we’re dealing with. In many cases, the IDs are returned by the query, and this method can extract them from the Query.
    • see: https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-015d5cec0dcc9f802dcfa99bc136f478R297
  • abstract public function get_node_by_id()
    • This method was added to tell the loader how to resolve the node as a Model, ensuring it gets properly passed through the Model layer
    • see: https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-015d5cec0dcc9f802dcfa99bc136f478R328-R335
  • Changed access of some methods from public to protected as they’re not intended to be used outside of the class or extending classes
    • get_amount_requested(), get_offset(), get_query_amount(), has_next_page(), has_previous_page(), get_start_cursor(), get_end_cursor(), get_nodes(), get_cursor_for_node(), get_edges(), get_page_info()

Registering GraphQL Fields with Arguments

One of the most common ways to customize the WPGraphQL Schema is to register new fields.

When registering fields, argument(s) can also be defined for the field.

Field arguments in GraphQL allow input to be passed to the field, and when the field is resolved, the input of the field argument can be used to change how the field is resolved.

Registering Fields

Below is an example of registering a field with an argument:

add_action( 'graphql_register_types', function() {

  $field_config = [
    'type' => 'String',
    'args' => [
      'myArg' => [
        'type' => 'String',
      ],
    ],
    'resolve' => function( $source, $args, $context, $info ) {
      if ( isset( $args['myArg'] ) ) {
        return 'The value of myArg is: ' . $args['myArg'];
      }
      return 'test';
    },
  ];

  register_graphql_field( 'RootQuery', 'myNewField', $field_config);
});

Let’s break down the code:

Hooking into WPGraphQL

add_action( 'graphql_register_types', function() { ... });

This action hooks into WPGraphQL when the WPGraphQL Schema is being generated. By hooking our code here, it makes sure our function is only executed when WPGraphQL is being used.

Define the Field Config

Within that action, we define a $field_config array which gets passed to the register_graphql_field() function.

$field_config = [
  'type' => 'String',
  'args' => [
    'myArg' => [
      'type' => 'String',
    ],
  ],
  'resolve' => function( $source, $args, $context, $info ) {
    if ( isset( $args['myArg'] ) ) {
      return 'The value of myArg is: ' . $args['myArg'];
    }
    return 'test';
  },
];

Within the field config we define the following:

  • type: We define the type as “String” to tell WPGraphQL that the field is a String in the Schema
  • args: We define an array of arguments that will be available to the field.
  • resolve: We define a function to execute when the field is queried in GraphQL. Resolve functions in GraphQL always receive 4 arguments ($source, $args, $context, $info). The argument we care about for this example is the 2nd one, $args. We check to see if that argument is set, and if it is, we append the value to the string “The value of myArg is:” and return it. Otherwise we just return the string “test”.

Register the Field

And now we can register the field using the $field_config we have defined:

register_graphql_field( 'RootQuery', 'myNewField', $field_config);

Here we use the register_graphql_field() function. It accepts 3 arguments:

  • The first argument is the Type in the Schema to add a field to. In this case, we want to add a field to the RootQuery Type.
  • The second argument is the name of the field we are registering. This should be unique on the Type, meaning the Type should not already have a field of this name.
  • The third argument is the $field_config, which we just reviewed.

The field in action

We can query this like so:

query {
  myNewField
}

and the results will be:

{
  "data": {
    "myNewField": "test"
  }
}

Now, we can pass a value to the argument like so:

query {
  myNewField( myArg: "something" )
}

and the results will be:

{
  "data": {
    "myNewField": "The value of myArg is: something"
  }
}

Now, you can introduce GraphQL variables like so:

query MyQuery($myArg:String) {
  myNewField( myArg: $myArg )
}

And then you can pass variables to the request. Here’s an example of using a variable in GraphiQL:

Screen Shot 2020-02-26 at 9 33 03 AM

Pair Programming on WPGraphQL for SEOPress

SEOPress is a popular SEO Plugin for WordPress.

Last night I paired with @moon_meister to work on his WPGraphQL for SEOPress plugin. We registered some custom Types and Fields to the WPGraphQL Schema to expose the SEOPress settings to WPGraphQL.

Screen recording of a pair programming session wit Alex Moon and Jason Bahl, working on the WPGraphQL for SEOPress plugin

WPGraphQL v0.6.0 Release

WPGraphQL v0.6.0 is here!

This release is one of the most significant releases in the history of the WPGraphQL plugin.

Breaking Changes

Before getting into the hype of the release, I want to highlight some prominent breaking changes. A full list can be found in the release notes.

  • A change to pagination of connections was introduced and any plugin/theme that has a class extending the AbstractConnectionResolver will need to add a public method is_valid_offset to support pagination properly. Not adding this method to extending classes will cause errors.
  • Many fields of Post Object Types (Post, Page, Custom) are now only exposed on the Type if that post_type supports the feature. For example, the “page” post type doesn’t support excerpts in core WordPress. Prior to this release you could query the field excerpt when querying for pages. You no longer can ask for the field excerpt on Pages because Pages don’t support excerpts. Posts do support excerpts, so you can query that field on Posts. If you were querying fields that a Post Type didn’t support, this release might break some of your queries.
  • We fixed a bug with settings registered with dashes not properly being mapped to the schema. This fix in behavior could cause a breaking change depending on what types of settings you had registered and were querying from the schema.

New Features

This release focused primarily on Interfaces. A full list of new features can be found on the release notes.

In GraphQL, an Interface is an abstract type that defines a certain set of fields that a type must include to implement the interface.

In WPGraphQL, prior to this release, the only Interface that was defined and implemented was the node interface, which defined the ID field. Any Type, such as Post, Page, Category, Tag, Comment or User, that implements the node interface must have an ID field. And thus, any of these types could also be fetched by ID.

Take the following query for example:

query GET_NODE {
  node(id:"cG9zdDoyMjE5") {
    __typename
    id
    ...on Post {
      title
    }
    ...on Page {
      title
      date
    }
  }
}

In the example query, we pass an ID of a node. If, during execution, the server determines the ID is that of a Post, it will return to us the __typename, id, and title, but if it is determined that the ID is that of a Page, it will return the __typename, id, title and date.

Interfaces allow us to ask for any Type that implements said interface and in response we can ask for the common fields, and can specify the different fields we want based on the Type that is resolved at execution.

This allows for applications to be built with a high level of predictability.

Your application is now able to predict what fields will be returned based on the Type being returned. This type of interaction with an API can eliminate entire classes of bugs.

New Interfaces

After this release, we now have many more interfaces to join the Node interface.

The following are Interfaces introduced in this release:

  • TermNode: Defines fields shared across Terms of any Taxonomy Type
  • ContentNode: Defines common fields shared by Post object nodes. Not all fields of Post Types are common across all Post Types. Each Post Type can register/deregister support for numerous fields. The fields that cannot be different are defined in this interface and all Post types implement this Interface.
  • UniformResourceIdentifiable: Defines a uri field. Implemented by Nodes that can be accessed by URI.
  • NodeWithTitle: Defines a title field. Implemented by post types that support titles.
  • NodeWithContentEditor: Defines the content field. Implemented by post types that support the editor.
  • NodeWithAuthor: Defines the author field, connecting a Node to its author. Implemented by post types that support authors.
  • NodeWithFeaturedImage: Defines the featured image field, connecting a Node to its featured image. Implemented by post types that support thumbnails.
  • NodeWithComments: Defines comment fields. Implemented by post types that support comments.
  • HierarchicalContentNode: Defines hierarchical fields (parent/children). Implemented by hierarchical post types.
  • ContentTemplate: Defines a template field.
  • NodeWithRevisions: Defines revisions fields. Implemented by post types that support revisions.
  • NodeWithExcerpt: Defines the excerpt field. Implemented by post types that support excerpts.
  • NodeWithTrackbacks: Defines fields related to trackbacks and pingbacks. Implemented by post types that support trackbacks.

I cannot provide examples of using every one of these interfaces, but I will cover some examples that should be valuable to WPGraphQL users.

Content Node Interface

The ContentNode interface allows for nodes of different Post Types to be queried in a single connection. Prior to this release, Posts of each type were fetched from their dedicated entry points. For example, you could query for { posts { ...fields } } or { pages { ...fields } }, but now, you can query a single collection spanning many post types.

Here’s an example:

{
  contentNodes {
    nodes {
      __typename
      id
      link
      uri
      ... on Page {
        isFrontPage
      }
    }
  }
}

And we might see results like the following:

{
  "data": {
    "contentNodes": {
      "nodes": [
        {
          "__typename": "Post",
          "id": "cG9zdDoyMjE5",
          "link": "http://acf.local/2020/01/14/test-revisions/",
          "uri": "2020/01/14/test-revisions/"
        },
        {
          "__typename": "Page",
          "id": "cGFnZToxNjQw",
          "link": "http://acf.local/test-page-revision/",
          "uri": "test-page-revision/",
          "isFrontPage": false
        },
      ]
    }
  }
}

In this example, we can see that we are receiving both a Post and a Page in the same connection query, and are able to specify common fields, but also able to specify a specific field we want if the Type is Page.

This flexibility allows WPGraphQL users to create robust User Interfaces with flexibility, but also predictability.

URI Interface

The UniformResourceIdentifiable Interface added in this release allows for any node that can be identified by URI to be queried for from a single entry point.

This is incredibly powerful for headless WordPress applications.

When interacting with a WordPress site, it’s incredibly common for a user to have a URI, a path to a resource. Typically that path is entered into a browser, and WordPress is able to return the proper page for that resource. The template and data displayed by WordPress is different based on the type of resource (page, post, user, term) it is.

WPGraphQL can now interact the same way.

A user can pass a URI to WPGraphQL, and specify what it wants in return based on the Type that the resource resolves as.

The following example shows how to query for a nodeByUri for a Post, Page, Tag, Category and User, specifying different fields for each Type.

{
  page: nodeByUri(uri: "about/") {
    ...URI
  }
  post: nodeByUri(uri: "2019/12/05/test-5/") {
    ...URI
  }
  tag: nodeByUri(uri: "tag/8bit/") {
    ...URI
  }
  category: nodeByUri(uri: "category/alignment/") {
    ...URI
  }
  user: nodeByUri(uri: "author/jasonbahl/") {
    ...URI
  }
}

fragment URI on UniformResourceIdentifiable {
  __typename
  uri
  ... on Page {
    pageId
  }
  ... on Post {
    postId
  }
  ... on Category {
    categoryId
  }
  ... on Tag {
    tagId
  }
  ... on User {
    userId
  }
}

And the response might look like:

{
  "data": {
    "page": {
      "__typename": "Page",
      "uri": "about/",
      "pageId": 1086
    },
    "post": {
      "__typename": "Post",
      "uri": "2019/12/05/test-5/",
      "postId": 1739
    },
    "tag": {
      "__typename": "Tag",
      "uri": "tag/8bit/",
      "tagId": 45
    },
    "category": {
      "__typename": "Category",
      "uri": "category/alignment/",
      "categoryId": 4
    },
    "user": {
      "__typename": "User",
      "uri": "author/jasonbahl/",
      "userId": 1
    }
  },
}

This is incredibly powerful for headless applications that might only have a URI of a resource to identify the resource by. Now headless applications can resolve resources with a high level of predictability, even without knowing the type of resource up front.

What’s next

Now that this monumental release is here, the next release will have a focus on Connections. There are many bugs and new features that we want to address that affect the behavior of connections. Keep an eye out for more details there.