Home / Blog / Headless WordPress with Next.js: picking between WPGraphQL and the REST API

Headless WordPress with Next.js: picking between WPGraphQL and the REST API

Choosing your data layer for a headless WordPress + Next.js site is a high-leverage decision. Here is how WPGraphQL and the WP REST API compare, and which project calls for which.

In a headless WordPress setup, the choice between the WP REST API and the WPGraphQL plugin shapes both your velocity and your long-term maintenance.

I’ve used REST on two projects and GraphQL on one. Here are the similarities, the differences, and the criteria I use to pick.

Where the WP REST API stands

In core since WP 4.7. No setup required. Endpoints:

  • GET /wp-json/wp/v2/posts – post list
  • GET /wp-json/wp/v2/posts/{id} – single post
  • GET /wp-json/wp/v2/pages – pages
  • GET /wp-json/wp/v2/categories – categories
  • GET /wp-json/wp/v2/users/{id} – user
  • POST /wp-json/wp/v2/posts – create post (auth required)

For a custom post type:

register_post_type('product', [
    'show_in_rest' => true,
    'rest_base' => 'products'
]);

Endpoint: /wp-json/wp/v2/products

REST API strengths

Built-in, zero setup. No plugin, nothing to install. Shipped in every WP version.

RESTful patterns. CRUD is URL-driven. Caching is HTTP-native (ETag, Cache-Control).

Authentication flexibility. Application passwords, JWT plugin, or OAuth plugin.

Consumer tooling. Every framework speaks REST. Native in Postman.

Low learning curve. Every developer knows REST.

REST API limitations

Over-fetching. Listing posts pulls every field, with no clean way to say “just title and URL”. The _fields parameter helps but is limited.

Multiple requests. Post + author + category means three endpoint calls. _embed can embed author or featured_media, but it’s not enough for complex nested queries.

Custom field exposure. ACF fields aren’t exposed by default. Each one needs register_rest_field boilerplate.

Filtering is limited. Meta queries aren’t direct in REST; you have to register custom filters.

The WPGraphQL plugin

WPGraphQL is a plugin that adds a GraphQL endpoint to WordPress. It maps the WP schema onto GraphQL types.

Setup:

  1. Install the plugin (from wordpress.org or via composer)
  2. Activate
  3. Endpoint: /graphql is available immediately
  4. GraphiQL lives in the admin (for debugging)

Sample query:

query GetPost($slug: String!) {
    postBy(slug: $slug) {
        title
        content
        author {
            node {
                name
                avatar {
                    url
                }
            }
        }
        categories {
            nodes {
                name
                slug
            }
        }
        featuredImage {
            node {
                sourceUrl
                altText
            }
        }
    }
}

One request returns post + author + categories + featured image. REST would need four.

GraphQL strengths

Single request, multi-resource. Nested queries collapse into one HTTP round trip.

Field selection. The client picks its fields, the server doesn’t send anything else. Payload stays small.

Type safety. Schema drives TypeScript (or any language) type generation. IDE autocompletion is excellent.

ACF support. With WPGraphQL for ACF, every ACF field lands in the GraphQL schema.

Subscriptions. Real-time updates are possible (rare, but available).

Developer experience. GraphiQL for schema exploration and query testing.

GraphQL pain points

Plugin dependency. WPGraphQL is a third-party plugin, not core. Community-maintained, no commercial support. If the maintainer steps away, you’ve got risk.

Learning curve. A team new to GraphQL has extra to absorb.

Caching is messier. HTTP cache doesn’t play well with a single endpoint and POST requests. CDN caching is hard. You need application-level caching (Apollo, urql).

Schema maintenance. New custom fields mean registering GraphQL types. WPGraphQL doesn’t expose them automatically.

Error handling. GraphQL errors come back inside 200 OK responses (in an errors array). Status-code-based error handling doesn’t work.

Performance comparison

Testing against the same dataset:

Scenario: blog post + 3 related posts + author + category.

REST API:
– 5 requests
– Total transfer: 48 KB
– Round-trip latency (3G): 1.8 seconds

GraphQL:
– 1 request
– Total transfer: 12 KB (with field selection)
– Round-trip latency (3G): 450 ms

On mobile or slow networks GraphQL is a clear win.

On desktop / fast networks the gap narrows.

Implementation in Next.js

With REST:

export async function getStaticProps({ params }) {
    const [post, categories, author] = await Promise.all([
        fetch(`${WP_URL}/wp-json/wp/v2/posts?slug=${params.slug}`).then(r => r.json()),
        fetch(`${WP_URL}/wp-json/wp/v2/categories`).then(r => r.json()),
        fetch(`${WP_URL}/wp-json/wp/v2/users/${post.author}`).then(r => r.json())
    ]);
    return { props: { post: post[0], categories, author } };
}

With GraphQL:

import { GraphQLClient } from 'graphql-request';

const client = new GraphQLClient(`${WP_URL}/graphql`);

export async function getStaticProps({ params }) {
    const query = `
        query($slug: String!) {
            postBy(slug: $slug) {
                title
                content
                author { node { name } }
                categories { nodes { name slug } }
            }
        }
    `;
    const data = await client.request(query, { slug: params.slug });
    return { props: { post: data.postBy } };
}

GraphQL is more compact, and the type-generation payoff lands on the frontend.

TypeScript + codegen

WPGraphQL plus GraphQL Code Generator auto-generates TS types:

npx graphql-codegen --config codegen.yml

codegen.yml:

schema: https://your-wp.com/graphql
documents: './src/**/*.graphql'
generates:
  ./src/generated/types.ts:
    plugins:
      - typescript
      - typescript-operations

On the frontend:

import { GetPostQuery } from './generated/types';

const data: GetPostQuery = await client.request(query);
console.log(data.postBy.title); // fully typed

With REST you end up writing types by hand or defining an OpenAPI spec.

When to pick which

Pick REST when:

  • Small project, fast setup
  • Team isn’t used to GraphQL
  • Public API for third-party consumers (broader tooling)
  • Cache-heavy use cases (HTTP cache)
  • You don’t want the WPGraphQL plugin dependency

Pick GraphQL when:

  • Mobile-first, network-sensitive
  • Complex nested data queries
  • TypeScript frontend
  • Heavy use of custom fields (ACF)
  • Developer experience is a priority

Authentication

Two authentication cases in headless setups:

Read-only public content: no auth. Posts, pages, products are publicly readable.

Write operations (comments, form submissions, user data): auth required.

REST API:
– Application passwords (core feature since WP 5.6)
– JWT plugin
– OAuth2 plugin

GraphQL:
– WPGraphQL supports the same auth methods
– Credentials in the request header

JWT is usually the cleanest for headless: bearer token, stateless, frontend-friendly.

Cache strategy

REST API + CDN:

  • Cache-Control: public, max-age=300 response header (via Nginx or a plugin)
  • CDN edge cache
  • Purge /wp-json/wp/v2/posts/{id} when the post updates

GraphQL + Apollo/urql client cache:

  • Server-side: WPGraphQL Smart Cache plugin (internal cache)
  • Client-side: Apollo Cache or urql cache
  • In Next.js: ISR regeneration (time-based, not request-by-request invalidation)

GraphQL doesn’t inherit REST’s HTTP-native cache. Invalidation gets more custom.

Maintenance comparison

Six to twelve months of project maintenance:

REST API: endpoints stay stable through WP major upgrades, breaking changes are rare. ACF REST exposure has to be maintained per field.

GraphQL: WPGraphQL plugin compatibility to watch. Custom type registration is an ongoing cost.

Both are manageable, but GraphQL has slightly more ceremony.

Final take

Decision matrix for a new headless WP project:

  1. Quick prototype: REST
  2. Production SaaS / mobile: GraphQL
  3. Public API exposure: REST
  4. ACF-heavy, complex schema: GraphQL
  5. Cache-critical: REST
  6. TypeScript-first: GraphQL

Both have pros and cons. It’s not “one right, one wrong”; it’s fit for the project.

My pattern: REST is enough for small-to-medium projects, and the GraphQL investment pays off on bigger, more complex ones.

Have a project on this topic?

Leave a brief summary — I’ll get back to you within 24 hours.

Get in touch