Development · Headless & Decoupled ·
Draft & Preview in a Headless
Draft & Preview in a Headless WordPress Setup
Introduction
Every WordPress editor knows the Preview button: click it, and a draft renders exactly as it will once published. That behavior depends entirely on a PHP template rendering the post on the WordPress site itself — which is precisely the piece a headless architecture removes. Without deliberate work, “Preview” in a headless setup either does nothing useful or points editors at a blank API response.
The Challenge
By default, clicking Preview generates a URL back to the WordPress install itself (?preview=true), rendered by a theme template that, in a headless project, isn’t showing visitors anything real. Editors lose the ability to see unpublished changes before they go live — a workflow regression serious enough that it alone can stall adoption of a headless rebuild internally, even when everything else works.
Standards and Best Practices
Filter preview_post_link to point editors at the frontend’s own dedicated preview route instead of the WordPress site, carrying a short-lived signed token rather than the post ID alone. That frontend preview route must call an authenticated endpoint capable of returning draft or pending content — never the same public endpoint used for published content, gated by nothing but a boolean flag. The public API should never return unpublished content by default under any circumstance; preview access must be an explicit, authenticated, time-limited exception, not a configuration toggle anyone can flip.
Practical Application
Redirecting the WordPress Preview button to the frontend, with a signed, expiring token instead of a raw post ID:
add_filter( 'preview_post_link', function( $link, $post ) {
$token = forwp_generate_preview_token( $post->ID ); // short-lived, signed
return sprintf(
'https://frontend.example.com/preview/%d?token=%s',
$post->ID,
$token
);
}, 10, 2 );
The resulting flow, end to end:
Potential Challenges
A preview link that leaks — logged somewhere public, shared in a chat tool with link previews enabled, or simply never expiring — turns draft content into effectively public content. A frontend’s own caching layer (ISR, a CDN) can accidentally cache a preview response and start serving draft content to real visitors if the preview route isn’t explicitly excluded from that caching. And Gutenberg’s own in-editor preview (rendered inside an iframe) expects the same data shape as the real page, which means the preview endpoint has to stay in sync with the published-content endpoint’s schema as both evolve.
Common Mistakes
Reusing the exact same public content endpoint for both published and draft content, gated by a single boolean parameter anyone can pass in a request. Issuing preview tokens that never expire, turning a “preview this draft” link into a permanent bypass of the publish workflow. Building the preview route to only handle the standard content shape and forgetting that revisions and autosaves are distinct from the post’s current draft state — pointing a preview token at the wrong one shows an editor content that isn’t what they just saved.
When It’s Better to Bring In a Specialist
Preview is deceptively small in scope and disproportionately disruptive when it’s wrong — a broken preview workflow erodes an editorial team’s trust in the entire headless rebuild, often before launch even happens. A WordPress developer who has shipped headless preview flows before knows the token-expiry, caching-exclusion, and revision-vs-autosave pitfalls up front, rather than discovering them from an editor’s bug report. Getting this specific piece reviewed is a small, targeted ask well within the scope of standard WordPress development services.
FAQ
It generates a link back to WordPress itself, rendered by a PHP theme template — but in a headless architecture, that template isn’t what visitors see, so the preview shows nothing meaningful. The link has to be rebuilt to point at the frontend instead.
Not by itself — anyone with the URL could then guess or enumerate other IDs. A signed, short-lived token tied to that specific post is the standard approach, so the link can’t be reused or extended arbitrarily.
No. Preview must call a distinct, authenticated endpoint capable of returning draft or pending content — never the public content endpoint gated by a flag, since that risks accidentally exposing draft content publicly.
Explicitly exclude the preview route from any caching layer, or mark its responses as non-cacheable at the HTTP level — a preview response should never be eligible to become a cached page for anyone else.
Yes. A preview token should resolve to the exact draft state the editor just saved — which post revision or autosave that corresponds to needs to be explicit in the token or the endpoint logic, not assumed.
Before it reaches editors in production — token expiry, cache exclusion, and revision handling are all cheaper to verify in a design review than to fix after an editorial team has already lost confidence in the workflow.
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.