Set up live preview

Session-based live preview shows your current Experience Orchestration canvas state on your website. Content, design, and slot changes can appear before you save. It supports experiences, experience fragments, components, and experience templates.

Live preview always renders an experience, including when you preview a component or an experience template.

The canvas creates a preview session and adds preview_session_id to your configured preview URL. Your website fetches the session’s current experience and receives updates through the SDK. You can use the same preview route for all supported canvas types.

For routes, authentication requirements, and WebSocket behavior, see the Preview Session API reference.

Follow the steps below to connect a new preview website. If the website already supports sessions, start at Configure preview URLs.

1. Connect your preview website

You need a website that can render Experience Orchestration content and an SDK configuration with your registered components and templates. The editor user needs permission to create a preview session.

Use the Content Preview API access token from Settings > API keys in the Contentful web app.

The Content Delivery API access token cannot be used to fetch preview sessions or receive live updates.

Use the adapter for your framework, or the framework-independent live preview client for a custom renderer.

FrameworkPackageLive integration
React@contentful/experiences-reactuseLivePreview
Svelte@contentful/experiences-svelteuseLivePreview
Angular@contentful/experiences-angularinjectLivePreview
Custom renderer@contentful/experiences-live-previewcreateLivePreviewClient

Read preview_session_id from the website URL and pass it to the SDK as sessionId. Supply the same spaceId and environmentId as the canvas, and use the Content Preview API token as previewToken.

The examples use experienceConfig for your registered components and templates, and previewConfig for your website’s configuration:

export const previewConfig = {
spaceId: '<space-id>',
environmentId: '<environment-id>',
previewToken: '<content-preview-api-token>',
};

This example uses fetchPreviewSession to retrieve the session’s render plan:

import { fetchPreviewSession } from '@contentful/experiences-react';
import { experienceConfig } from '../experience-config';
import { previewConfig } from '../preview-config';
import { Preview } from './Preview';
export const dynamic = 'force-dynamic';
export default async function PreviewPage({ searchParams }: {
searchParams: Promise<{ preview_session_id?: string }>;
}) {
const { preview_session_id: sessionId } = await searchParams;
const { spaceId, environmentId, previewToken } = previewConfig;
if (!spaceId || !environmentId || !previewToken || !sessionId) {
throw new Error('Preview requires a space, environment, token, and session ID.');
}
const currentPlan = await fetchPreviewSession(
{ spaceId, environmentId, sessionId },
{ previewToken },
{ config: experienceConfig },
);
return (
<Preview
initialPlan={currentPlan}
previewSessionOptions={{ spaceId, environmentId, sessionId, previewToken }}
/>
);
}

useLivePreview resolves incoming updates for ClientExperienceRenderer:

'use client';
import {
ClientExperienceRenderer,
useLivePreview,
type PortableRenderPlan,
type PreviewSessionOptions,
} from '@contentful/experiences-react';
import { experienceConfig } from '../experience-config';
export function Preview({ initialPlan, previewSessionOptions }: {
initialPlan: PortableRenderPlan;
previewSessionOptions: PreviewSessionOptions;
}) {
const preview = useLivePreview({
initialPlan,
previewSessionOptions,
resolveOptions: { config: experienceConfig },
});
return (
<>
{preview.error && (
<p role="status">
Live updates are unavailable. Showing the last valid preview.
</p>
)}
<ClientExperienceRenderer
experience={preview.data}
config={experienceConfig}
/>
</>
);
}

Fetch and live updates

fetchPreviewSession returns a render plan for the current session state. Live subscriptions receive subsequent experience payloads, which must be resolved with the same configuration before rendering.

Framework adapters handle resolution and keep the last valid plan if the connection fails. Custom integrations handle resolution and plan retention in their renderer.

If the SDK reports that live updates are unavailable, the canvas reloads the preview iframe after each successful session update. Without a live preview integration, the canvas reloads the iframe after you save.

The live subscription requires previewToken in the browser.

If your website requires Contentful credentials to remain server-side, implement a backend relay for live updates. Keep tokens out of the configured preview URL.

For cross-space content, encode the referenced spaces’ Content Preview API tokens. Pass the same value as resourceResolution in the first fetchPreviewSession argument and in previewSessionOptions for live updates.

See the SDK guide for renderer configuration.

2. Configure preview URLs

Allow embedding

Contentful displays your preview website in an iframe. If the iframe shows “Refused to connect”, check the preview page’s response headers in your browser’s developer tools: open Network, select the page request, then open Headers > Response Headers.

Update your preview website’s security headers:

  • Remove the X-Frame-Options header from the preview page’s response.
  • If you use Content-Security-Policy, allow https://app.contentful.com in its frame-ancestors directive. Keep the other directives in your existing policy.
Content-Security-Policy: frame-ancestors https://app.contentful.com;

If your preview website uses an authentication cookie, set SameSite=None and Secure so the browser can send it inside the iframe:

Set-Cookie: auth=<value>; SameSite=None; Secure

Add preview URLs

In the Contentful web app, go to Settings and select Content preview settings. Click + Create preview platform to add a preview configuration. Under Select one entity type, select All Experience entities to use a single preview URL for all experiences, experience fragments, components, and experience templates.

Enter your website’s preview URL:

https://your-preview-website.example/preview

The canvas appends preview_session_id to this URL.

An All Experience entities URL can include {entity.sys.*} tokens for website routing, for example {entity.sys.id} and {entity.sys.type}. The canvas replaces each token with the corresponding value from the entity being previewed. The session SDK doesn’t require these tokens.

Alternatively, select Experience to configure a target preview URL for experiences that use a selected experience template. You can also set a fallback URL for experiences that use other templates.

Save your changes and reload the canvas. If you cannot change content preview settings, contact your space administrator.

3. Open and verify live preview

In an experience or experience fragment canvas, select your website under Preview URL. Edit content, a design property, or a slot. The website should update before you save.

In a component or experience template canvas, open Settings > View settings > Live preview. Enable it and select your website.

The browser remembers the Live preview setting separately for components and experience templates.

For supported entry and asset parameters, use Select content to preview to choose sample content for the session.

Troubleshooting

What you seeWhat to check
The page shows saved content instead of unsaved edits.Read preview_session_id from the URL and pass it as sessionId to fetchPreviewSession. Fetching the saved experience with fetchExperience doesn’t retrieve the session’s unsaved edits.
The website loads in a separate tab but not inside the canvas.Follow Allow embedding to configure your website’s security headers and authentication cookies.
The initial preview renders, but no updates arrive and no error is reported.Supply both sessionId and previewToken in previewSessionOptions. Without them, the SDK doesn’t open a connection. A custom client also needs a subscribe listener to start receiving updates.
Live updates stop and the preview reports LivePreviewConnectionError.The live connection failed or its retries were exhausted. The last valid preview remains available. Check the preview token, session ID, and sessionHost, then reload the preview iframe to reconnect.
The preview stays unchanged after an invalid update or a session error message.The SDK ignores these messages and keeps the last valid data without reporting a connection error. Set previewSessionOptions.debug to true to log ignored messages in the browser console.
fetchPreviewSession throws NotFoundError.The session wasn’t found. Check spaceId, environmentId, and sessionId. If the session was deleted or expired, reopen the canvas and use the new preview session URL.
fetchPreviewSession throws PreviewSessionFetchError.The initial request failed or returned an invalid response. Inspect the error’s message and cause. Check your Content Preview API token, permissions, and HTTP host, then retry.
The preview iframe refreshes only after you save.Use a framework adapter, or connect previewClient.subscribeStatus(sendPreviewStatus) for a custom renderer so the canvas can coordinate updates.

Session and connection details

  • The default HTTP host is https://preview.xdn.contentful.com. Override it with host in the fetch options. The default WebSocket host is wss://preview.xdn.contentful.com. Override it with sessionHost in the subscription options.
  • Sessions expire 30 days after creation or the last successful session update. Viewing a session doesn’t extend its lifetime.
  • Up to 20 preview connections can use one session. Active connections must use the same preview token and resource-resolution value.