WordPress

WPGraphQL: Using WordPress as a Headless CMS

WPGraphQL is an open-source WordPress plugin that adds a fully featured GraphQL API to any WordPress installation. It transforms WordPress from a self-contained CMS into a content platform queryable by any GraphQL client — a Next.js front end, a mobile app, a Hugo build process, or any other consumer capable of making HTTP requests.

The REST API built into WordPress core works, but GraphQL solves problems the REST API does not handle elegantly: over-fetching unnecessary fields, under-fetching requiring multiple round-trips, and querying relational data efficiently. For complex publishing front ends, WPGraphQL is a more capable interface.

Installing WPGraphQL

Install through the WordPress plugin directory or via WP-CLI:

wp plugin install wp-graphql --activate

Once active, a GraphQL endpoint is available at /graphql. The plugin adds a GraphiQL IDE to the WordPress admin under GraphQL → GraphiQL IDE — an interactive browser for exploring the schema and testing queries.

Basic Queries

WPGraphQL exposes all standard WordPress content through the schema. Query recent posts:

query GetRecentPosts {
  posts(first: 10) {
    nodes {
      id
      title
      date
      slug
      excerpt
      author {
        node {
          name
        }
      }
      categories {
        nodes {
          name
          slug
        }
      }
      featuredImage {
        node {
          sourceUrl
          altText
          mediaDetails {
            width
            height
          }
        }
      }
    }
  }
}

This single query returns posts with their authors, categories, and featured images — data that would require four separate REST API calls to assemble. That efficiency is GraphQL’s core advantage.

Querying a Single Post

query GetPost($slug: ID!) {
  post(id: $slug, idType: SLUG) {
    title
    content
    date
    modified
    slug
    tags {
      nodes {
        name
        slug
      }
    }
    author {
      node {
        name
        description
        avatar {
          url
        }
      }
    }
    seo {
      title
      metaDesc
      opengraphImage {
        sourceUrl
      }
    }
  }
}

The seo field requires the WPGraphQL for Yoast SEO or WPGraphQL for RankMath companion plugins to expose SEO metadata through the schema.

Fetching in Next.js

A typical data fetching function for a Next.js headless WordPress front end:

const API_URL = process.env.WORDPRESS_API_URL;

async function fetchAPI(query, variables = {}) {
  const res = await fetch(API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables }),
  });
  
  if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
  
  const json = await res.json();
  if (json.errors) throw new Error(json.errors[0].message);
  
  return json.data;
}

// Get all post slugs for static generation
export async function getAllPostSlugs() {
  const data = await fetchAPI(`
    query AllSlugs {
      posts(first: 1000) {
        nodes { slug }
      }
    }
  `);
  return data.posts.nodes;
}

// Get a single post by slug
export async function getPost(slug) {
  const data = await fetchAPI(`
    query GetPost($slug: ID!) {
      post(id: $slug, idType: SLUG) {
        title
        content
        date
        slug
        excerpt
      }
    }
  `, { slug });
  return data.post;
}

Custom Post Types and ACF

WPGraphQL exposes custom post types registered with show_in_graphql: true:

register_post_type('press_release', [
  'public'           => true,
  'show_in_graphql'  => true,
  'graphql_single_name' => 'pressRelease',
  'graphql_plural_name' => 'pressReleases',
]);

For Advanced Custom Fields, the WPGraphQL for ACF companion plugin exposes field group data automatically once the field group is configured with GraphQL enabled. Custom fields appear as typed fields on their parent type in the schema — no custom resolver code required for standard ACF field types.

Authentication and Draft Content

Published content requires no authentication. To query draft posts, private posts, or perform mutations, include a Basic Auth or application password header:

const res = await fetch(API_URL, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Basic ${Buffer.from('user:app-password').toString('base64')}`,
  },
  body: JSON.stringify({ query, variables }),
});

WPGraphQL supports mutations for creating and updating content — useful for custom editorial tools that write back to WordPress — but most headless publishing implementations are read-only from the front end.

Preview Mode

One of the more complex aspects of headless WordPress is content preview. An editor who saves a draft in WordPress needs to see it rendered in the front-end application before publishing. WPGraphQL’s preview support, combined with Next.js’s draft mode (formerly preview mode), enables this:

  1. Configure a preview webhook in WordPress that calls a Next.js preview route
  2. The preview route authenticates and fetches the draft via WPGraphQL using credentials
  3. Next.js renders the draft content in preview mode
  4. The editor sees the rendered page in their browser without publishing

The configuration is non-trivial but well-documented in the WPGraphQL and Next.js documentation. Several starter templates (the Faust.js framework from WP Engine is the most complete) handle this wiring.

WPGraphQL vs the REST API

Both APIs expose the same content. GraphQL is the better choice when your front end has complex data requirements — multiple content types, relational data, conditional fields — that would require many REST calls or return excessive unused data. The REST API is simpler to work with for straightforward queries and requires no additional plugin.

For most headless WordPress projects with a React-based front end, WPGraphQL is worth the setup.