4.2 KiB
4.2 KiB
| title | aliases | tags | sources | created | updated | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Admin Preview & Draft Preview |
|
|
|
2026-05-15 | 2026-05-15 |
Overview
Preview generates a direct link from the Admin Edit View to your front-end app. A Preview button appears in the edit view with an href pointing to the URL your function returns.
Not wiki/payloadcms/admin-panel-overview — Live Preview embeds your app in an iframe inside the Admin Panel. Preview just navigates to an external URL.
Setup
Add admin.preview to any Collection or Global config:
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
preview: ({ slug }) => `http://localhost:3000/${slug}`,
},
}
Function Signature
preview: (doc, options) => string | null | Promise<string | null>
| Arg | Type | Description |
|---|---|---|
doc |
object | Full document data including unsaved changes |
options.locale |
string | Current locale |
options.req |
PayloadRequest | Full request object |
options.token |
string | JWT of authenticated user |
Build absolute URLs using req:
preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}`
Draft Preview
Draft Preview allows editors to see unpublished content. Flow:
- Admin clicks Preview → navigates to a custom endpoint on the front-end
- Endpoint verifies a shared
PREVIEW_SECRET, authenticates the user viapayload.auth() - Endpoint enables Next.js Draft Mode (sets a cookie) and redirects to the page
- Page query includes
draft: true→ Payload returns draft document
Next.js Implementation (3 Steps)
Step 1 — Format Preview URL
preview: ({ slug }) => {
const params = new URLSearchParams({
slug,
collection: 'pages',
path: `/${slug}`,
previewSecret: process.env.PREVIEW_SECRET || '',
})
return `/preview?${params.toString()}`
}
Step 2 — Create /app/preview/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
export async function GET(req) {
const payload = await getPayload({ config: configPromise })
const { searchParams } = new URL(req.url)
const path = searchParams.get('path')
const previewSecret = searchParams.get('previewSecret')
if (previewSecret !== process.env.PREVIEW_SECRET)
return new Response('Forbidden', { status: 403 })
if (!path?.startsWith('/'))
return new Response('Bad path', { status: 400 })
let user
try {
user = await payload.auth({ req, headers: req.headers })
} catch {
return new Response('Forbidden', { status: 403 })
}
const draft = await draftMode()
if (!user) { draft.disable(); return new Response('Forbidden', { status: 403 }) }
draft.enable()
redirect(path)
}
Step 3 — Query Draft Content
const { isEnabled: isDraftMode } = await draftMode()
const page = await payload.find({
collection: 'pages',
draft: isDraftMode,
overrideAccess: isDraftMode,
where: { slug: { equals: slug } },
limit: 1,
})?.then(({ docs }) => docs?.[0])
Conditional Preview Button
Return null to hide the button:
preview: (doc) => doc?.enabled ? `http://localhost:3000/${doc.slug}` : null
Useful when you only want preview available for documents meeting certain criteria (e.g. has a slug, is a specific status).
Key Takeaways
admin.previewreturns a URL string (ornullto hide button) — can be async- Preview ≠ Live Preview: Preview is a link, Live Preview is an embedded iframe
- Draft Preview requires 3 parts: preview URL →
/previewroute → draft-aware page query - Always guard the preview route with a
PREVIEW_SECRETenv var +payload.auth()check - Use
req.protocol + req.hostfor absolute URLs (required for Vercel Preview Deployments) - Return
nullconditionally to show/hide the preview button based on document state
Related
Sources
raw/admin__preview.md— compiled from https://payloadcms.com/docs/admin/preview