Content Manager Preview#
The Strapi v5 Content Manager Preview feature lets editors open a live preview of their content in a front-end application from within the admin panel. It is configured in config/admin.ts under a preview key and surfaces as an "Open preview" button in the Content Manager edit view.
Key source locations:
| File | Role |
|---|---|
preview-config.ts | Reads, validates, and exposes the preview configuration |
preview.ts | Server service: resolves a preview URL by invoking the handler |
PreviewSidePanel.tsx | Admin UI panel: shows the "Open preview" button or a setup prompt |
Preview Configuration (preview-config.ts)#
The createPreviewConfigService exposes three methods critical to the preview lifecycle:
isConfigured()#
Returns true when preview is either explicitly disabled (enabled === false) or a handler function is present . A missing or null handler — e.g., after enabled: true is set but no handler has been defined — causes isConfigured() to return false, which the server service converts to a NotFoundError (see below).
validate()#
Called at startup. Throws a ValidationError if the handler is not a function . This enforces the contract early rather than at request time.
getPreviewHandler()#
Returns the configured handler, or a no-op emptyHandler when preview is disabled . Callers never need to null-check the handler.
Async handlers: Since PR #25396, the handler signature may return a
Promise. The type definition in@strapi/typeswas broadened to(uid, params) => string | null | undefined | Promise<string | null | undefined>and the server serviceawaits the result .
NotFoundError Handling in getPreviewUrl()#
The server-side getPreviewUrl method is the runtime entry point:
- Calls
config.isConfigured(). - If not configured → throws
errors.NotFoundError('Preview config not found'). This signals "no config found" rather than a handler runtime failure. - If configured →
awaits the user-defined handler . Any exception from the handler is caught and re-thrown asApplicationErrorto avoid leaking implementation details .
The NotFoundError is intentionally distinct from the ApplicationError so the front end can branch on it.
Admin UI: PreviewSidePanel.tsx#
The panel uses RTK Query to pre-fetch the preview URL for the current document . The URL itself is not rendered here; it just primes the cache so the subsequent /preview route loads instantly.
NotFoundError → "Set up preview" CTA#
When the query returns a NotFoundError, the panel replaces the "Open preview" button with a tertiary "Set up preview" link pointing to docs.strapi.io/cms/features/preview . This was added in PR #23961 to improve feature discoverability.
Remount latch (PR #27043)#
Two separate mechanisms prevent the preview button from being silently lost during document state transitions (e.g., locale/status settling after load or save):
-
isPreviewEnabledlatch — a boolean state that flips totruethe first time apreviewUrlis observed and never resets . The panel only returnsnullwhen!isPreviewEnabled && (!data?.data?.url || error), so transient empty-data states don't hide the button mid-interaction. -
ConditionalTooltipalways-mounted — theTooltipwrapper is always kept in the React tree; visibility is toggled via the controlledopenprop (open={isShown ? undefined : false}) rather than conditional rendering . This prevents the child button from remounting when tooltip visibility changes, which would swallow clicks.
Configuration Reference#
The preview block lives in config/admin.ts :
preview: {
enabled: true,
config: {
allowedOrigins: env('CLIENT_URL'), // Added to frame-src CSP
async handler(uid, { documentId, locale, status }) {
const document = await strapi.documents(uid).findOne({ documentId });
// return a URL string, or null to disable preview for this content type
},
},
}
allowedOriginsis automatically injected into thestrapi::securityCSPframe-srcdirective .- The handler returning
nulldisables the preview button for that specific content type without any error . - The
statusparameter ('draft'|'published') enables the handler to route to draft vs. published URLs .