Type something to search...
How to Enable and Use WPGraphQL in WordPress?

How to Enable and Use WPGraphQL in WordPress?

WPGraphQL is a free plugin that adds a GraphQL API to WordPress, letting a front end request exactly the fields it needs, across posts, media, authors, and taxonomies at once, in a single query instead of several separate REST calls. It's the most widely used GraphQL implementation for WordPress and a common alternative (or companion) to the WordPress REST API in headless setups.

This guide covers installing WPGraphQL, exploring its schema with GraphiQL, writing queries that pull related data in one request, filtering and paginating results, and calling it from a Next.js front end.

Installing WPGraphQL

WPGraphQL is a standard WordPress plugin, install it the normal way:

  1. In the WordPress admin, go to Plugins → Add New Plugin.
  2. Search for "WPGraphQL," find the plugin by WPGraphQL, and click Install Now, then Activate.
  3. A new GraphQL menu item appears in the admin sidebar, along with a single endpoint at /graphql (for example, https://example.com/graphql).

Unlike the REST API, which exposes a separate URL per resource type (/wp-json/wp/v2/posts, /wp-json/wp/v2/media, and so on), WPGraphQL exposes exactly one endpoint. Every query, no matter what data it asks for, is a POST request to that same /graphql URL.

Exploring the Schema with GraphiQL

WPGraphQL ships with an in-admin GraphiQL IDE (GraphQL → GraphiQL IDE) for writing and testing queries against your actual site data before you write any front-end code. It shows the full schema on the right-hand side, every type, field, and argument WPGraphQL exposes, and autocompletes as you type, which is the fastest way to discover what's queryable without reading documentation first.

Your First Query: Fetching Posts

A basic query for a list of posts, their titles, and their excerpts:

query GetPosts {
  posts(first: 10) {
    nodes {
      id
      title
      slug
      excerpt
      date
    }
  }
}

Run this in GraphiQL, or send it as a POST request from any client:

const query = `
  query GetPosts {
    posts(first: 10) {
      nodes {
        id
        title
        slug
        excerpt
        date
      }
    }
  }
`;

const res = await fetch("https://example.com/graphql", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query }),
});

const { data } = await res.json();
console.log(data.posts.nodes);

Notice the response shape mirrors the query's shape exactly, data.posts.nodes is an array of objects containing only the fields you asked for, nothing more. This is the core advantage over REST: no over-fetching a large, fixed response shape when you only need three fields.

Fetching Related Data in One Request

This is where WPGraphQL earns its keep. A single query can pull a post's featured image, author, and categories, data that would take several REST requests (or the _embed parameter, with a fixed shape) to assemble:

query GetPostWithRelations($slug: ID!) {
  post(id: $slug, idType: SLUG) {
    title
    content
    date
    featuredImage {
      node {
        sourceUrl
        altText
      }
    }
    author {
      node {
        name
        avatar {
          url
        }
      }
    }
    categories {
      nodes {
        name
        slug
      }
    }
  }
}

Variables ($slug here) are passed alongside the query rather than interpolated into the query string:

const query = `
  query GetPostWithRelations($slug: ID!) {
    post(id: $slug, idType: SLUG) {
      title
      content
      featuredImage {
        node {
          sourceUrl
          altText
        }
      }
    }
  }
`;

const res = await fetch("https://example.com/graphql", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    query,
    variables: { slug: "hello-world" },
  }),
});

const { data } = await res.json();

Filtering, Sorting, and Paginating

WPGraphQL's where argument handles filtering, similar to REST's query parameters but nested inside the query itself:

query PostsByCategory {
  posts(first: 5, where: { categoryName: "wordpress", orderby: { field: DATE, order: DESC } }) {
    nodes {
      title
      slug
    }
  }
}

Pagination uses cursor-based pagination (the Relay connection spec) rather than page numbers, which handles content changing between requests more reliably than offset-based pagination:

query PaginatedPosts($after: String) {
  posts(first: 10, after: $after) {
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      title
      slug
    }
  }
}

Fetch the next page by passing the previous response's pageInfo.endCursor back in as the after variable.

Using WPGraphQL from a Next.js Front End

A minimal Next.js server component fetching a post through WPGraphQL:

// app/blog/[slug]/page.tsx
async function getPost(slug: string) {
  const res = await fetch("https://cms.example.com/graphql", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      query: `
        query GetPost($slug: ID!) {
          post(id: $slug, idType: SLUG) {
            title
            content
            featuredImage {
              node { sourceUrl altText }
            }
          }
        }
      `,
      variables: { slug },
    }),
    next: { revalidate: 3600 },
  });

  const { data } = await res.json();
  return data.post;
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);

  return (
    <article>
      <h1>{post.title}</h1>
      {post.featuredImage && (
        // eslint-disable-next-line @next/next/no-img-element
        <img
          src={post.featuredImage.node.sourceUrl}
          alt={post.featuredImage.node.altText ?? ""}
        />
      )}
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

This is largely the same shape as the REST-based Next.js integration, just a single POST request to /graphql instead of one or more GET requests to REST endpoints.

Writing a Mutation

Reading data covers most headless front-end needs, but WPGraphQL also supports mutations for creating and updating content, gated behind authentication exactly like a REST API write. A mutation creating a draft post looks like this:

mutation CreateDraftPost($title: String!, $content: String!) {
  createPost(input: { title: $title, content: $content, status: DRAFT }) {
    post {
      id
      slug
      status
    }
  }
}

Sent the same way as a query, just with a different query string value and, critically, an Authorization header carrying a valid token:

const res = await fetch("https://example.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${jwtToken}`,
  },
  body: JSON.stringify({
    query: `
      mutation CreateDraftPost($title: String!, $content: String!) {
        createPost(input: { title: $title, content: $content, status: DRAFT }) {
          post { id slug status }
        }
      }
    `,
    variables: { title: "Draft from the API", content: "<p>Body text.</p>" },
  }),
});

Handling GraphQL Errors

Unlike a REST API, WPGraphQL almost always responds with a 200 status code, even when something went wrong, errors are reported inside the response body's errors array rather than through the HTTP status:

{
  "data": { "post": null },
  "errors": [
    {
      "message": "No post exists with id: bad-slug",
      "extensions": { "category": "user" }
    }
  ]
}

Always check for an errors key in the parsed response before assuming data is complete or correct, a partial response can include both real data and errors simultaneously, for example, when one field in a larger query fails to resolve but the rest of the query succeeds.

Using a Typed Client Instead of Raw fetch()

The raw fetch() calls above work fine, but larger projects often reach for a small GraphQL client to handle request boilerplate, TypeScript typing, and caching. graphql-request is a common lightweight choice:

import { GraphQLClient, gql } from "graphql-request";

const client = new GraphQLClient("https://example.com/graphql");

const query = gql`
  query GetPost($slug: ID!) {
    post(id: $slug, idType: SLUG) {
      title
      content
    }
  }
`;

const data = await client.request(query, { slug: "hello-world" });

For projects already using Apollo Client or urql on the front end, WPGraphQL works as a standard GraphQL endpoint with either, no WordPress-specific client library is required, since WPGraphQL implements the standard GraphQL specification rather than a WordPress-proprietary variant.

Extending the Schema for Custom Fields

If you use Advanced Custom Fields, the companion WPGraphQL for ACF plugin automatically adds your ACF field groups to the GraphQL schema, no manual field registration needed. For fields that don't come from ACF, you can register a field directly in PHP:

add_action( 'graphql_register_types', function () {
    register_graphql_field( 'Post', 'readingTime', [
        'type'        => 'Int',
        'description' => 'Estimated reading time in minutes',
        'resolve'     => function ( $post ) {
            $word_count = str_word_count( wp_strip_all_tags( $post->contentRaw ) );
            return (int) ceil( $word_count / 200 );
        },
    ] );
} );

After this, readingTime becomes a queryable field on the Post type, alongside every built-in field, resolvable in the exact same query as everything else.

Custom Post Types and WPGraphQL

A custom post type only appears in the GraphQL schema if it was registered with show_in_graphql set to true, along with graphql_single_name and graphql_plural_name:

register_post_type( 'project', [
    'public'              => true,
    'show_in_rest'        => true,
    'show_in_graphql'     => true,
    'graphql_single_name' => 'project',
    'graphql_plural_name' => 'projects',
    // ...other args
] );

Once registered this way, project and projects become queryable fields on the root Query type, following the same nodes/pageInfo shape as the built-in posts field.

Debugging Schema Changes

After registering a custom field or exposing a new post type, the change doesn't always show up immediately in GraphiQL's autocomplete due to WPGraphQL's internal schema caching. Two things worth checking when a field seems to be missing:

  • Clear the WPGraphQL schema cache under GraphQL → Settings, or programmatically via the graphql_flush_schema action, after registering new types or fields in PHP.
  • Confirm graphql_register_types (or the equivalent registration hook) actually ran. A common mistake is calling register_graphql_field() too early, before WPGraphQL has initialized its own type registry, which silently does nothing rather than throwing a visible error.

Frequently Asked Questions (FAQ) About WPGraphQL

No, they run independently and don't conflict. Many sites use both: WPGraphQL for the front end's main content queries, and the REST API for specific integrations (webhooks, third-party plugins) that already expect it.

No, it's a third-party plugin, though a very widely adopted one in the headless WordPress community, with an active maintainer team and a large plugin ecosystem of its own (WPGraphQL for ACF, WPGraphQL for WooCommerce, and others).

Yes, through GraphQL mutations, which follow the same authenticated-request model as REST API writes. Most setups use JSON Web Tokens for mutation authentication, via a companion plugin like WPGraphQL JWT Authentication.

It needs show_in_graphql set to true at registration, along with graphql_single_name and graphql_plural_name. show_in_rest being true doesn't automatically expose it to GraphQL; the two are configured independently.

Yes, for authenticated requests with sufficient permissions, following the same rule as the REST API: unauthenticated requests only ever see published, public content, regardless of what the query asks for.

Not automatically, both ultimately run the same underlying WordPress database queries. The performance win comes from making fewer round trips (one GraphQL request instead of several REST requests) and from not transferring fields you don't need, which matters most on pages that combine many related pieces of data.

WPGraphQL exposes registered nav menus and their items as a menuItems field out of the box, which is generally simpler than the custom endpoint work required to expose menus over the REST API.

Conclusion

WPGraphQL turns WordPress into a single, flexible GraphQL endpoint where a front end asks for exactly the fields it needs, across posts, media, authors, and custom fields, in one request. Installing it is a standard plugin install, and GraphiQL gives you a way to explore and test the full schema against your real content before writing any front-end code.

The tradeoff against the REST API is mostly about shape and round trips, GraphQL avoids over-fetching and lets you assemble related data in one request, while REST's fixed endpoints are simpler to reason about for straightforward cases. Many headless WordPress projects use both side by side, GraphQL for the front end's main queries, REST for specific integrations that expect it. If you haven't already, it's worth comparing this directly against the REST API for your specific use case before committing to one.

A few additional resources if you want to go deeper:

Tags :
Share :

Related Posts

Effortlessly Crafting Compelling WordPress Pages

Effortlessly Crafting Compelling WordPress Pages

As a website owner or content creator, having the ability to seamlessly add new pages to your WordPress site is crucial. Whether you're introducing a

Continue Reading
High Traffic Tips for WordPress Mastery 🚥

High Traffic Tips for WordPress Mastery 🚥

In our digital age, where online visibility is paramount, ensuring your WordPress site can handle surging traffic is crucial. Just like a finely-tune

Continue Reading
How Do I Change the WordPress Login URL?

How Do I Change the WordPress Login URL?

By default, every WordPress site's login page lives at the same predictable address: yoursite.com/wp-login.php (which also happens to redirect from

Continue Reading