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:
- Configure a preview webhook in WordPress that calls a Next.js preview route
- The preview route authenticates and fetches the draft via WPGraphQL using credentials
- Next.js renders the draft content in preview mode
- 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.