Development · Headless & Decoupled ·
REST API Backend
Building a REST API Backend for Headless WordPress
Introduction
The WP REST API ships with WordPress and is already turned on — wp/v2/posts works the moment you ask for it. That’s exactly why it’s the wrong foundation for a headless frontend to build on directly: it was designed to power the block editor, not to be a public-facing content API, and its defaults reflect that job, not yours.
The Challenge
wp/v2 is verbose by design — full rendered HTML content, _links for every relation, taxonomy and author data reachable only through _embed. It exposes exactly what the internal WordPress admin needs and nothing about what a specific frontend needs. On top of that, its write endpoints assume an authenticated admin user is behind the request; a public frontend calling them directly is either wide open or completely blocked, with no middle ground built in.
Standards and Best Practices
Build a dedicated, versioned namespace (4wp/v1, not wp/v2) for anything the frontend actually consumes, and shape each endpoint’s response to exactly what that page needs — not the full WordPress object graph. Use register_rest_field to add computed or reshaped fields onto existing endpoints when you only need a small addition, and register_rest_route for anything with different data or access rules entirely. Split read-only public endpoints from authenticated write endpoints explicitly — never rely on the same route silently behaving differently based on login state. Validate and sanitize every input server-side regardless of what the frontend already checked; the API has to defend itself from any client, not just the one you built.
Practical Application
How a request actually resolves once it reaches a custom endpoint:
A custom endpoint built specifically for a frontend’s needs, following the same namespace pattern used across 4WP’s own headless plugin:
add_action( 'rest_api_init', function() {
register_rest_route( '4wp/v1', '/services', array(
'methods' => 'GET',
'callback' => 'forwp_get_services',
'permission_callback' => '__return_true',
) );
register_rest_route( '4wp/v1', '/contact', array(
'methods' => 'POST',
'callback' => 'forwp_handle_contact_submission',
'permission_callback' => '__return_true',
'args' => array(
'email' => array( 'required' => true, 'type' => 'string', 'format' => 'email' ),
'message' => array( 'required' => true, 'type' => 'string' ),
),
) );
} );
function forwp_get_services( WP_REST_Request $request ) {
$services = get_posts( array( 'post_type' => 'forwp_service', 'numberposts' => -1 ) );
return array_map( function( $post ) {
return array(
'id' => $post->ID,
'title' => get_the_title( $post ),
'icon' => get_post_meta( $post->ID, 'icon', true ),
);
}, $services );
}
A minimal endpoint contract, kept explicit rather than inherited from wp/v2 defaults:
| Field | Type | Notes |
|---|---|---|
id | integer | Internal ID, never exposed as the public URL |
title | string | Plain text, already resolved server-side |
icon | string | Frontend-specific field, has no wp/v2 equivalent |
Potential Challenges
List endpoints that need related data — an author, a featured image, taxonomy terms — risk N+1 query patterns if each item fetches its relations independently; _embed solves this for wp/v2 but a custom endpoint has to do its own eager-loading deliberately. Nothing in the REST API rate-limits requests by default, so a public endpoint under real traffic needs that handled at the server or CDN layer. And once a frontend depends on a specific response shape, any change to that shape is now a breaking change across two codebases, not a same-repo refactor.
Common Mistakes
Exposing wp/v2/posts directly to a public frontend with no field restriction, shipping full post content, internal meta, and embed links nobody asked for. Omitting permission_callback entirely on a custom route — in current WordPress this triggers a deprecation notice and, worse, defaults to open access, not the restrictive behavior many developers assume. Ignoring pagination on the frontend and calling a list endpoint expecting “all of it,” which quietly turns into a full-table dump once the content set grows past a handful of items.
When It’s Better to Bring In a Specialist
A REST API surface is one of those things that’s cheap to design correctly the first time and expensive to fix once a frontend depends on it in production. A WordPress developer experienced in headless REST design reviews the namespace, field shape, and permission model against the frontend’s actual needs before it ships — catching the exposure and pagination issues above at design time rather than in a production incident. That kind of review is a standard part of professional WordPress development services on any headless build.
FAQ
Build a custom namespace for anything a public frontend depends on directly. Extending wp/v2 with register_rest_field is fine for small internal additions, but a frontend-facing contract deserves its own versioned space that WordPress core updates can’t reshape underneath you.
The built-in _fields query parameter (?_fields=id,title) works on wp/v2 endpoints and narrows the response to exactly those fields — useful for internal tooling, but a dedicated custom endpoint is still the safer choice for a public frontend contract.
Yes, as of recent WordPress versions omitting it triggers a deprecation notice. Set it explicitly — __return_true for genuinely public data, or a real capability/token check for anything else — rather than leaving it implicit.
Fetch related data (authors, terms, meta) in a single batched query keyed by the IDs already in the result set, rather than querying per item inside a loop — the same eager-loading principle _embed uses internally on wp/v2.
Yes, but not with WordPress’s default cookie+nonce auth, which assumes same-origin. A cross-origin frontend needs a token-based scheme (Application Passwords or a JWT/OAuth layer) — covered in full on the Auth page.
Before the frontend starts consuming it in production, ideally at the point the endpoint contract is first drafted — changing field names or shapes afterward means coordinating a release across the WordPress plugin and every frontend consumer at once.
Headless & Decoupled
WordPress as a content backend — REST or GraphQL APIs, auth, preview, and modern JavaScript frontends.
Headless & Decoupled
When and how to decouple WordPress from the frontend — architecture, trade-offs, and 4WP production patterns.
REST as Headless Backend
Expose posts, pages, and custom content via WP REST for SPAs and static frontends.
GraphQL (WPGraphQL)
Flexible queries with WPGraphQL — schemas, connections, and headless data fetching.
Auth & Permissions
Application passwords, JWT, OAuth, and permission callbacks for decoupled clients.
Preview & Revalidation
Draft preview, on-demand revalidation, and keeping headless frontends in sync with WordPress.
Frontend (Next.js / SPA)
Next.js, React, and static frontends consuming WordPress — routing, ISR, and the 4WP Headless App.