The GraphQL API for WordPress ships with tons of special features, destined to help you make the most out of GraphQL.

Already integrated to WordPress permalink

The plugin already maps the WordPress data model, enabling to fetch any piece of data:

  • Posts
  • Custom Posts
  • Pages
  • Users
  • User Roles
  • Comments
  • Tags
  • Categories
  • Custom Taxonomies
  • Meta
  • Media
  • Menus
  • Settings

Interactive clients permalink

The plugin offers user-friendly tools to interact with the GraphQL service.

Executing the GraphQL queries is done with GraphiQL:

Executing a query via GraphiQL
Executing a query via GraphiQL

Interactively visualizing the GraphQL schema is done with GraphQL Voyager:

Interacting with the schema via GraphQL Voyager
Interacting with the schema via GraphQL Voyager

Multiple layers of security permalink

Several mechanisms have been put in place to help protect the data:

👉🏽 We can expose pre-defined data through persisted queries, and avoid granting public access to the single endpoint.

👉🏽 We can create custom endpoints, setting permissions for every field in the schema through access control lists, and exposing their schema through their own public clients.

👉🏽 We can define the API to be either public or private.

👉🏽 The single endpoint, and clients to interact with it, are disabled by default.

👉🏽 Which settings (from table wp_options) and meta values (from tables wp_postmeta, wp_usermeta, wp_commentmeta and wp_taxonomymeta) can be queried must be explicitly defined in the configuration.

👉🏽 Some fields are exposed as "unrestricted" fields", as to provide access to private data, but have it disabled by default: public data (eg: posts) is accessible by default, private data from the user (myPosts) is available to the logged-in user, and admin data (unrestrictedPosts) must be explicitly enabled.

This image shows an interactive schema client, which must be enabled to grant public access to it:

Public Voyager client, disabled by default
Public Voyager client, disabled by default

Custom endpoints permalink

A GraphQL server normally exposes a single endpoint for retrieving and posting data, providing a unique behavior for all users and applications.

With custom endpoints, we can create and expose a custom GraphQL schema under its own URL, and grant access to this endpoint to a specific target:

👉🏽 Some client or user
👉🏽 A group of users with more access to features (such as PRO users)
👉🏽 One of the several applications, like mobile app or website
👉🏽 3rd-party APIs
👉🏽 Any other

This video demonstrates publishing a custom endpoint:

Persisted queries permalink

In a REST API, we create multiple endpoints, each returning a pre-defined set of data. In a GraphQL API, in contrast, we provide any query to a single endpoint, which returns exactly the requested data.

Persisted queries are normal GraphQL queries, however they are stored in the server and accessed under their own URL, thus emulating a REST endpoint. They provide the advantages from these two APIs, while avoiding their disadvantages:

✅ Accessed via GET or POST❌ Accessed only via POST
✅ Can be cached on the server or CDN❌ Needs to provide an extra layer in client-side just for caching
✅ It's secure: only intended data is exposed❌ Data is exposed to anyone, including malicious actors
✅ No under/over fetching of data, all data is retrieved in a single request❌ It can be slow, since the application may need several requests to retrieve all the data
✅ It enables rapid iteration of the project❌ It's tedious to create all the endpoints
✅ It can be self-documented❌ Producing documentation is mandatory
✅ It provides clients to create and publish the query❌ Publishing endpoints is done via code

This video demonstrates creating a persisted query:

Access control lists permalink

The GraphQL endpoint, which can return any piece of data accessible through the schema, could potentially allow malicious actors to retrieve private information. Hence, we must implement security measures to protect the data.

With access control lists, we can define who can access each field and directive in the schema:

👉🏽 Disable access to everyone
👉🏽 Grant access if the user is logged-in, or logged-out
👉🏽 Grant access if the user has some role
👉🏽 Grant access if the user has some capability

This video demonstrates creating permissions for different fields in the schema:

Public/private API mode permalink

What should happen when a user without access to some field or directive in the schema, attempts to access it?

With the public/private API mode we can control the desired behavior:

In the public API, the fields in the schema are exposed, and when the permission is not satisfied, the user gets an error message with a description of why the permission was rejected.

In the private API, the schema is customized to every user, containing only the fields available to him or her, and so when attempting to access a forbidden field, the error message says that the field doesn't exist.

This video demonstrates making an API either public or private:

HTTP caching permalink

Because it sends the queries via POST, GraphQL is normally not cacheable on the server-side or intermediate stages between the client and the server (such as a CDN), and we need to worry about adding a caching layer on the application on the client-side, making it slower and more complex.

However, because persisted queries can be accessed via GET, their response can be cached through standard HTTP caching. We can define for how long every field or directive must be cached, and the response will include a Cache-Control header, whose max-age value is calculated automatically from all the fields and directives in the requested query (or no-store if it involves user state).

This video demonstrates how the API response makes use of HTTP caching:

API hierarchy permalink

An API may expose several endpoints which are somehow related to each other, and which may execute a similar query. This is the case, for instance, when creating endpoints that exposes the data in one language or another.

Through the API Hierarchy we can define a structure for endpoints, so that we can produce:

  • /graphql/posts/english/
  • /graphql/posts/french/

In this case, a parent query posts can provide the GraphQL query, and its descendant queries english and french provide the variables to customize the query.

This video demonstrates creating an API hierarchy:

Schema namespacing permalink

If plugins WooCommerce and Easy Digital Downloads both implemented a Product type for the GraphQL API, then we could not install both plugins at the same time, since their types would conflict.

Schema namespacing enables to avoid conflicts in the schema, by having all type names namespaced. Hence, type Product would become Woo_Product and EDD_Product respectively, and these types could be added to the same schema.

This images shows a namespaced schema:

Namespaced schema
Namespaced schema

Nested mutations permalink

Mutations are only exposed in the root type in GraphQL. As a consequence, the root type becomes heavily bloated, containing fields with nothing in common among themselves other than being mutations (which is a technical matter, not an interface design decision).

Nested mutations make the schema more logical and browsable, by enabling to perform mutations on any type, and not only on the root type. They help performance too, by allowing to modify data on the result from another mutation, thus avoiding the latency from executing multiple requests.

This GraphQL query demonstrates a nested mutation:

mutation {
createPost(title: "First title") {
update(title: "Second title", content: "Some content") {
addComment(comment: "My first comment") {

"Unrestricted" admin fields permalink

The GraphQL schema must strike a balance between public and private fields, as to avoid exposing private information in a public API.

By default, all fields in the GraphQL schema can only access public data. For instance, posts can only retrieve posts with status "publish".

In addition, we can add "unrestricted" fields to the schema, expected to be used by the admin only, enabled for a specific custom endpoint or persisted query, which can also fetch private data.

For instance, field unrestrictedPosts can retrieve posts with status "publish", "pending", "draft" or "trash", for any user.

# These are published posts only
posts {

# These posts can have any status
unrestrictedPosts(status:[publish, pending, draft, trash]) {