Headless WordPress

REST API vs WPGraphQL for Headless WordPress

How the two approaches differ in practice for a headless build — over-fetching, round trips, caching, tooling and maintenance — and which suits which project.

By 6 min read
Network diagram with a central WordPress node linked to connected services

Once you've decided to go headless, the next decision is how the frontend gets its data. WordPress ships a REST API in core; WPGraphQL is a plugin that adds a GraphQL endpoint. Both work, and plenty of successful sites run on each.

The trade-offs are real but narrower than the debate suggests. Here's what actually differs once you're building.

The fundamental difference

REST gives you fixed endpoints that return fixed shapes. /wp/v2/posts returns posts, with whatever fields the endpoint decided to include. If you need the author's name too, that's a second request, or _embed which returns the whole author object.

GraphQL gives you one endpoint and lets the client describe exactly what it wants: these posts, with these five fields, plus each author's name and avatar, plus the featured image in this size — in a single request that returns exactly that and nothing else.

Everything else follows from that difference.

Over-fetching and round trips

This is GraphQL's clearest win. A blog index needing title, date, excerpt and author name gets exactly those fields in one request. The REST equivalent either returns full post objects with rendered content you throw away, or makes additional requests for the related data.

On a fast connection this is a minor inefficiency. On a mobile connection in a patchy area, the difference between one 8KB response and three 60KB responses is the difference between a page that appears and one that doesn't.

REST can narrow the gap. The _fields parameter trims the response to named fields, and a purpose-built custom endpoint returns precisely what one screen needs in one request. That's the pragmatic REST answer: don't use the generic endpoints for a specific screen.

Caching, where REST wins

REST responses are ordinary GET requests at distinct URLs. That means a CDN can cache them at the edge with no special knowledge, browsers cache them, and a reverse proxy in front of WordPress works without configuration.

GraphQL sends queries as POST requests to a single endpoint, which defeats all of that by default. You can cache GraphQL — persisted queries turn known queries into GET requests with cacheable URLs, and WPGraphQL has its own caching layer — but it's configuration you have to do rather than behaviour you get.

For a mostly static site rebuilt at deploy time this hardly matters, because the queries run at build time. For a site fetching at request time under real traffic, it matters a lot.

Tooling and developer experience

  • GraphQL's schema is introspectable, so the GraphiQL explorer shows you every available field and validates queries as you write them. That is genuinely excellent.
  • Types can be generated from the schema, so a TypeScript frontend gets accurate types for every query automatically.
  • REST's discovery is thinner: the namespace index and per-route schemas exist, but nothing approaching the same experience.
  • On the other hand, a REST endpoint can be tested with curl and read in a browser. Debugging a GraphQL query needs a client that speaks GraphQL.

The plugin dependency

This is the consideration people underweight. REST is in core: it's maintained by the WordPress project, it will not be abandoned, and it's covered by core's backwards-compatibility commitments.

WPGraphQL is a third-party plugin. It's well-maintained and widely used, but it's still a dependency your entire frontend cannot function without. If it lags a major WordPress release, or if the extension you need for ACF or WooCommerce falls behind, that's your site's data layer.

That's not a reason to avoid it — plenty of production sites depend on it happily. It is a reason to check the maintenance status of every extension you'll need before you commit, and to factor it into the risk conversation with a client.

Custom fields and third-party data

Neither approach exposes custom fields automatically. In REST you register meta with show_in_rest, or add a REST field for anything structured. In GraphQL you register the field in the schema, usually via an extension for whichever field plugin you're using.

Check this early, because it's where the effort actually lands in a headless build. A site with a rich ACF structure, a custom post type per content type and relationships between them needs work in either approach — and the availability and quality of the bridging extensions differs by field plugin.

WooCommerce deserves specific mention: it has a mature REST API of its own, and the GraphQL equivalent is a separate extension. If the frontend needs commerce data, check what's available before choosing.

Performance on the server

A GraphQL query that traverses deeply related data can generate a lot of database work in one request — the classic N+1 problem, where fetching ten posts with their authors becomes eleven queries. WPGraphQL uses data loaders to batch these, but a badly written query can still be expensive.

REST has the same risk inside a poorly written callback, but the fixed response shape makes the cost predictable: you know what /wp/v2/posts costs because it always does the same work. With GraphQL, the client decides how expensive a request is, which is powerful and occasionally alarming.

If you expose GraphQL publicly, limit query depth and complexity. An unbounded nested query is a denial of service waiting to be discovered.

How to decide

  • Static site generation, content fetched at build time → either works; pick on developer preference, since caching and round trips barely matter.
  • A frontend fetching deeply related data at request time → GraphQL's single request is a real advantage.
  • A simple frontend fetching lists and single items → REST with _fields and a couple of custom endpoints is less machinery.
  • Edge caching is central to your architecture → REST is much less work.
  • A team already fluent in GraphQL with an existing Apollo or Relay setup → use what they know.
  • Minimising dependencies matters to the client → REST is in core.

A middle position

Nothing stops you using both. REST for the simple, cacheable public reads — posts, pages, menus — and GraphQL for the one complex screen that genuinely needs relational fetching. Or REST throughout, with a handful of custom endpoints purpose-built for the screens that would otherwise need three requests.

In practice that second option covers most projects I build. A custom endpoint returning exactly one screen's data is fast, cacheable, has no plugin dependency, and is maybe forty lines of code.

Frequently asked questions

Is WPGraphQL faster than the REST API?

For the client, often yes — one request returning only the needed fields beats several returning too much. On the server it can be slower, because a deeply nested query does more work in one request. And REST caches at the edge for free, which frequently outweighs the request-count advantage under real traffic.

Do I need WPGraphQL for a headless WordPress site?

No. The core REST API is enough for most headless builds, especially with a few purpose-built endpoints for the screens that need composed data. WPGraphQL earns its place when the frontend genuinely needs to fetch deeply related data in one request.

Can I use both REST and GraphQL on the same site?

Yes, and it's a reasonable position. Use REST for simple, cacheable public reads and GraphQL for the one or two screens with complex relational needs. Both read the same WordPress data, so there's no synchronisation problem — just two ways in.

What happens if WPGraphQL stops being maintained?

Your frontend's data layer stops receiving updates, which becomes a problem at the next major WordPress release. It's a well-maintained plugin with wide adoption, so this isn't an imminent risk, but it's a dependency worth naming in a risk conversation — particularly for the extensions bridging ACF or WooCommerce.

How do I expose ACF fields to a headless frontend?

In REST, register the fields with show_in_rest or add a REST field for anything structured. In GraphQL, you'll need the relevant bridging extension to add them to the schema. Either way it's real work, and it's worth checking extension availability and maintenance before you commit to an approach.

Topics

  • WPGraphQL
  • WordPress REST API
  • headless WordPress
  • GraphQL WordPress