How Live Preview works
Live Preview is a coordinated, session-scoped conversation between three participants: the CMS where editors work, your running website, and the preview services that serve draft content. The CMS signals that something changed, your site decides how to re-render, and the preview services authorize and return draft data on demand.
This chapter covers the architecture, the session model, the APIs, and the communication protocol. Every concept is explained once here; the rendering strategy chapters that follow build on this foundation.
The Mental Model
When an editor opens an entry with Live Preview enabled, three concurrent sessions begin:
The content editing session lives inside the entry editor. The CMS tracks every keystroke and field change, maintaining the authoritative state of draft content.
The preview rendering session runs in your site, loaded in an iframe within the CMS or in a separate browser tab. Your site initializes the Live Preview SDK, establishes communication with the CMS, and renders content using your normal rendering pipeline.
The data access session operates through the Preview API, which serves draft content scoped by a preview token and a live preview hash. The hash acts as a session identifier, preventing draft content from leaking across sessions.
These sessions are tightly coordinated but loosely coupled. The CMS doesn't need to know how your site renders. Your site doesn't need to understand the CMS's internal state. The Preview API doesn't care about your framework.
The Flow of a Single Edit
An editor changes the About page headline from "Our Story" to "Our Mission" and tabs out of the field:
Session establishment: The CMS creates a Live Preview session and generates a unique hash
Site loading: Your site loads with the hash in the URL as a query parameter, along with content type UID, entry UID, and locale
Handshake: The SDK sends an initialization message to the CMS, the CMS acknowledges, and the communication channel opens
Change notification: The CMS emits a "content changed" event. This event does not contain the updated content — it only signals that something changed
Refetch and re-render: Your site fetches draft content from the Preview API using the current hash, then re-renders
Every edit follows this same pattern. The simplicity is deliberate — it makes the system predictable across frameworks.
Two Architectural Consequences
Live Preview never bypasses your data layer. It always forces a refetch, so your rendering path stays honest. If your site has a bug, Live Preview exposes it rather than hiding it.
Live Preview never pushes content into the DOM. The CMS doesn't know your component structure, doesn't manipulate your state, and doesn't inject HTML. Your site receives a signal and decides what to do with it. Problems in preview almost always trace back to how you fetch and cache content, not to the CMS or SDK.
The Five Components
1. Contentstack CMS (The Orchestrator)
Hosts the entry editor where content modifications occur
Detects changes as editors type, save, or publish
Generates and rotates the live preview hash
Loads your site in an iframe panel or opens it in a new browser tab
Emits change events via postMessage
The CMS never pushes draft content directly to your site. It only signals that changes occurred.
2. Your Website (The Renderer)
Renders content using your normal component structure
Initializes the Live Preview SDK when loaded in preview context
Listens for change events from the CMS
Fetches draft content from preview services when appropriate
Your site remains in control of its rendering pipeline.
3. The Live Preview SDK (The Mediator)
Establishes the postMessage handshake between CMS and your site
Tracks session and hash state
Adapts behavior for CSR vs SSR rendering models
Exposes a clean API for subscribing to change events
Manages the edit button and other UI affordances
The SDK is intentionally minimal. It doesn't fetch content, manage state, or render anything.
4. Preview Services (The Draft Data Source)
Preview services serve unpublished content with special authentication:
Preview token: A credential from your stack settings that authorizes access to draft content
Live preview hash: The session identifier that scopes which draft content you can access
REST Preview Endpoints by Region:
Region | Endpoint |
|---|---|
North America | rest-preview.contentstack.com |
AWS EU | eu-rest-preview.contentstack.com |
Azure NA | azure-na-rest-preview.contentstack.com |
Azure EU | azure-eu-rest-preview.contentstack.com |
GCP NA | gcp-na-rest-preview.contentstack.com |
GCP EU | gcp-eu-rest-preview.contentstack.com |
GraphQL Preview Endpoints:
Region | Endpoint |
|---|---|
North America | graphql-preview.contentstack.com |
AWS EU | eu-graphql-preview.contentstack.com |
5. Delivery Services (The Published Data Source)
The production-safe endpoints for published content. Optimized for stability, caching, and performance. Don't require the live preview hash and won't serve draft content.
Quick Orientation: The Skeleton in Code
Here is how the five components map to the kickstart code from the introduction:
// Component 5: Delivery Services + Component 4: Preview Services
import contentstack, { QueryOperation } from "@contentstack/delivery-sdk";
const stack = contentstack.stack({
apiKey: "...",
deliveryToken: "...",
environment: "preview",
live_preview: {
enable: true,
preview_token: "...",
host: "rest-preview.contentstack.com",
},
});
// Component 3: The SDK (Mediator)
import ContentstackLivePreview, { IStackSdk } from "@contentstack/live-preview-utils";
ContentstackLivePreview.init({
ssr: false,
mode: "builder",
stackSdk: stack.config as IStackSdk,
stackDetails: { apiKey: "...", environment: "..." },
editButton: { enable: true },
});
// Component 2: Your Website (Renderer)
ContentstackLivePreview.onEntryChange(async () => {
const result = await stack
.contentType("page").entry().query()
.where("url", QueryOperation.EQUALS, "/")
.find();
renderPage(result.entries?.[0]);
});Preview API vs Delivery API
The Preview API and Delivery API share the same REST routes, GraphQL schemas, query structure, and response shape. The difference is what content each serves and what authentication each requires.
Delivery API serves published content. Requires a delivery token. Fully cacheable. Use for production.
Preview API serves draft content including unsaved changes. Requires both a preview token AND the live preview hash. Never cacheable — the same request can return different content milliseconds later as the editor types. Use only during active preview sessions.
This distinction is a trust boundary. Mixing preview and delivery requests in the same flow produces a page with inconsistent data — part published, part draft — that matches neither the editor's view nor the production site.
Authentication
// Delivery API headers
{
"api_key": "your_stack_api_key",
"access_token": "your_delivery_token"
}
// Preview API headers
{
"api_key": "your_stack_api_key",
"access_token": "your_delivery_token",
"preview_token": "your_preview_token",
"live_preview": "current_session_hash"
}The Switch Logic
Centralize the decision. If you have a hash, use preview. If you don't, use delivery.
function getContentstackConfig(previewHash) {
const baseConfig = {
apiKey: process.env.CONTENTSTACK_API_KEY,
deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
environment: process.env.CONTENTSTACK_ENVIRONMENT
};
if (previewHash) {
return {
...baseConfig,
host: 'rest-preview.contentstack.com',
previewToken: process.env.CONTENTSTACK_PREVIEW_TOKEN,
livePreviewHash: previewHash
// IMPORTANT: Disable any caching
};
}
return {
...baseConfig,
host: 'cdn.contentstack.io'
// Caching is safe here
};
}The Session Lifecycle and Hash
A Live Preview session begins when an editor opens an entry and ends when they close it. Everything in between is scoped to a single transient token: the live preview hash.
What the Hash Is
The hash is a short-lived, session-scoped token that proves:
A valid preview session exists
The request is authorized (combined with the preview token)
The request is scoped to this editing session
The hash is runtime state, not a stack setting or environment variable. It's generated fresh for each editing session, can rotate during long sessions, and becomes invalid when the session ends.
Strict Rules
Do not store the hash in a database. It's runtime state, not persistent data.
Do not place the hash in a long-lived cookie. Cookies persist across sessions. The hash must not.
Do not cache preview responses keyed by URL. Two requests to the same URL with different hashes return different content.
Do not share the hash between users. Each editing session has its own hash.
The Lifecycle
Session creation: Editor opens an entry. CMS generates a unique hash.
Site load: CMS loads your site in an iframe (or new tab) with query parameters: live_preview (hash), content_type_uid, entry_uid, locale.
Handshake: SDK sends init to the CMS, receives init-ack. Communication channel is open.
Steady state: Editor types → CMS emits change event → SDK calls your callback → you refetch and re-render. This loop repeats for every edit.
Session end: Editor closes the entry. Hash becomes invalid. Preview API requests with the old hash fail.
The session outlives navigation within the preview iframe (clicking links), but it does not outlive the editor's engagement with the entry.
Hash Rotation
During long editing sessions, the CMS may rotate the hash. Always read the current hash from the SDK (CSR) or from the current request parameters (SSR) rather than storing it at initialization.
Communication Protocol
Live Preview uses the browser's postMessage API for cross-origin messaging between the CMS and your site (in the iframe). The SDK abstracts this into a clean event model.
The Event Protocol
init (Site → CMS): Sent when the SDK initializes. Tells the CMS your site is ready.
init-ack (CMS → Site): Confirms the channel is open and the handshake is complete.
client-data-send (CMS → Site): Emitted when content changes. Does not contain the actual content — only signals that something changed. Your site responds by refetching.
Events Carry Intent, Not Payload
This is a critical design principle. When you receive a change event, it means "something changed" — not "here's the new content." Your site decides what to fetch. Your data flow stays deterministic. Your fetch layer is the source of truth.
Attempting to extract field changes or apply deltas from events will fail. The events don't contain that information.
Iframe vs New Tab
By default, Live Preview loads your site in an iframe within the CMS entry editor. Starting with SDK v4.0.0, Contentstack also supports opening the preview in a standalone browser tab — enable "Always Open in New Tab" in Settings > Live Preview.
The new-tab mode bypasses iframe restrictions (SSO, OAuth, X-Frame-Options, strict CSP headers). Both modes use the same postMessage communication. The SDK detects the context and adapts.
You can check the current context through ContentstackLivePreview.config.windowType:
"preview": iframe-based Live Preview or Timeline preview
"builder": Visual Builder iframe
"independent": direct browser access
Frequently asked questions
What is Live Preview, conceptually?
Live Preview is a session-scoped, event-driven conversation between the CMS, your running site, and preview services. The CMS signals changes; your site refetches draft data and re-renders.
What are the three sessions involved in Live Preview?
The content editing session (in the CMS editor), the preview rendering session (in your site), and the data access session (via the Preview API). They are coordinated but loosely coupled through well-defined interfaces.
Does the CMS push updated content to the browser during Live Preview?
No. The CMS emits a change event without content payload, and your site decides how to refetch draft content from the Preview API and re-render using its normal data flow.
What is the live preview hash used for?
It identifies the active editing session and scopes access to draft content. It helps prevent draft content from leaking across sessions or persisting after the session ends.
Why shouldn’t preview responses be cached?
Preview data is ephemeral and session-scoped. Caching can serve stale drafts, break the refetch loop, and risk leaking draft content across sessions.