01 — What it is
A GraphQL layer on top of WordPress.
WPGraphQL is a free, open-source plugin that adds an extendable GraphQL API to any WordPress site. Your post types, taxonomies, users, media and custom fields become one typed schema. The frontend sends a query to a single endpoint and gets back only the fields it asked for.
One endpoint
Every read goes to /graphql. No list of routes to learn: the schema is the documentation.
Typed schema
Built from what your site already has. Plugins and your own code extend it with register_graphql_field.
Open source
GPL-3.0, installed from WordPress.org like any plugin. Extensions for the IDE, Smart Cache and ACF live in the same official repository.
02 — How it works
Ask for a shape. Get that shape back.
One post page needs the post, its author, categories and featured image. With default REST routes that is several requests or a heavy _embed. With WPGraphQL it is one query.
- QueryThe frontend describes the fields it needs and sends them to
/graphql, asGETfor public reads. - ValidateWPGraphQL checks every field and type against the schema before anything touches the database.
- ResolveResolvers load the data. Built-in loaders batch related lookups by ID across the whole list.
- RespondJSON comes back in exactly the shape of the query: no extra fields, no second trip.
query PostBySlug($slug: ID!) {
post(id: $slug, idType: SLUG) {
title
date
author { node { name } }
categories { nodes { name slug } }
featuredImage {
node { sourceUrl altText }
}
}
}{ "data": { "post": {
"title": "…",
"date": "…",
"author": { "node": { "name": "…" } },
"categories": { "nodes": [ … ] },
"featuredImage": {
"node": { "sourceUrl": "…", "altText": "…" }
}
} } }03 — Examples
Three patterns you use from day one.
From our hands-on build: WPGraphQL + Next.js, step by step.
query Posts($first: Int!, $after: String) {
posts(first: $first, after: $after) {
pageInfo { hasNextPage endCursor }
nodes { slug title date }
}
}Pass endCursor back as after. Cursors are opaque: build “Older / Newer”, not page numbers.
add_action( 'graphql_register_types', function () {
register_graphql_field( 'Post', 'readingTime', array(
'type' => 'Int',
'resolve' => function ( $post ) {
// words / 200, min 1
},
) );
} );Register once in WordPress, and every client can query readingTime.
fragment PostCard on Post {
slug
title
date
excerpt
readingTime
}The list, the single page and “related posts” share one shape. Change the card, edit one fragment.
Query terminal · coming soon
Build a WPGraphQL query step by step, wizard style, and see the response shape before you write a line of frontend code.
04 — Where it’s used
Wherever WordPress is the content backend.
Headless frontends
Next.js with the App Router reads posts through server components and plain fetch. No client library needed for reads.
One WordPress, many channels
A site, a mobile app and an embedded widget query the same schema, each asking only for what it shows.
Blocks as components
WPGraphQL Content Blocks returns Gutenberg blocks as typed data, so each block type maps to your own component.
Custom fields
WPGraphQL for ACF exposes field groups with Show in GraphQL. Native meta goes through register_graphql_field.
05 — Benefits
Why developers reach for it.
One request
Related types in a single round trip instead of a REST waterfall.
Exact shape
No over-fetching: each component asks for exactly what it displays.
Self-documenting
A typed schema you can explore in GraphiQL from the WordPress admin.
Extensible
register_graphql_field and register_graphql_type keep custom data consistent with core types.
Cacheable
GET plus persisted queries make public reads cacheable by URL, at the CDN and in Next.js.
06 — Challenges
Easy to start. Lock it before launch.
The default setup is developer-friendly, not production-safe. The flexibility that makes GraphQL attractive is also its biggest risk.
- Introspection. Public introspection exposes the whole schema. Keep it off; logged-in developers still have GraphiQL.
- Query depth. Turn on depth limiting with a max your real queries fit into.
- Page size. Cap connections:
graphql_connection_max_query_amount→ 50. - Caching. A
POSTwith a query body has no URL to cache on. UseGET+ persisted queries. - N+1. A custom resolver that queries per node multiplies DB calls. Batch through loaders.
- Schema sprawl. Two plugins adding the same field name to one type is a real conflict. Review names before launch.
- Logic in resolvers. Keep resolvers thin and call the same service layer your REST endpoints use.
07 — FAQ
Questions we get about WPGraphQL.
Should a new headless project use GraphQL or REST?
GraphQL when pages routinely assemble several related content types and REST would mean several round trips or a heavy _embed. REST when most pages need one resource with light relations: it is simpler to build, cache and debug.
Should I send GraphQL requests as GET or POST?
GET for public reads, so responses are cacheable by URL. POST for mutations and authenticated requests.
Do I need Apollo Client or urql with Next.js?
Not for server components. Plain fetch with Next.js caching covers reads. A client library earns its place with heavy client-side state, optimistic updates or subscriptions.
Does WPGraphQL work with ACF?
Yes. Install WPGraphQL for ACF and enable Show in GraphQL on the field group. The fields appear on the post type in the schema.
Can I render Gutenberg blocks as React components?
Yes, with WPGraphQL Content Blocks: blocks come back as typed data instead of one HTML string, and you map each block type to a component.
How do I secure a public GraphQL endpoint?
Disable public introspection, enforce depth limits, cap page size and move to persisted queries, so the server only executes a known, approved set of queries.
WPGraphQL name, logo and brand colours © WPGraphQL contributors, used to refer to the project (GPL-3.0). 4wp.dev is an independent educational series, not affiliated with WPGraphQL. Primary source: wpgraphql.com.