Back to Payload

Preview

docs/admin/preview.mdx

3.84.18.1 KB
Original Source

Preview is a feature that allows you to generate a direct link to your front-end application. When enabled, a "preview" button will appear on the Edit View within the Admin Panel with an href pointing to the URL you provide. This will provide your editors with a quick way of navigating to the front-end application where that Document's data is represented. Otherwise, they'd have to determine that URL themselves which is not always straightforward especially in complex apps.

The Preview feature can also be used to achieve something known as "Draft Preview". With Draft Preview, you can navigate to your front-end application and enter "draft mode", where your queries are modified to fetch draft content instead of published content. This is useful for seeing how your content will look before being published. More details.

<Banner type="warning"> **Note:** Preview is different than [Live Preview](../live-preview/overview). Live Preview loads your app within an iframe and renders it in the Admin Panel allowing you to see changes in real-time. Preview, on the other hand, allows you to generate a direct link to your front-end application. </Banner>

To add Preview, pass a function to the admin.preview property in any Collection Config or Global Config:

ts
import type { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    preview: ({ slug }) => `http://localhost:3000/${slug}`,
  },
  fields: [
    {
      name: 'slug',
      type: 'text',
    },
  ],
}

Options

The preview function resolves to a string that points to your front-end application with additional URL parameters. This can be an absolute URL or a relative path, and can run async if needed.

The following arguments are provided to the preview function:

PathDescription
docThe data of the Document being edited. This includes changes that have not yet been saved.
optionsAn object with additional properties.

The options object contains the following properties:

PathDescription
localeThe current locale of the Document being edited.
reqThe Payload Request object.
tokenThe JWT token of the currently authenticated user.

If your application requires a fully qualified URL, such as within deploying to Vercel Preview Deployments, you can use the req property to build this URL:

ts
preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line

Draft Preview

The Preview feature can be used to achieve "Draft Preview". After clicking the preview button from the Admin Panel, you can enter into "draft mode" within your front-end application. This will allow you to adjust your page queries to include the draft: true param. When this param is present on the request, Payload will send back a draft document as opposed to a published one based on the document's _status field.

To enter draft mode, the URL provided to the preview function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here vary from framework to framework although the underlying concept is the same.

Next.js

If you're using Next.js, you can do the following code to enter Draft Mode.

Step 1: Format the Preview URL

First, format your admin.preview function to point to a custom endpoint that you'll open on your front-end. This URL should include a few key query search params:

ts
import type { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    preview: ({ slug }) => {
      const encodedParams = new URLSearchParams({
        slug,
        collection: 'pages',
        path: `/${slug}`,
        previewSecret: process.env.PREVIEW_SECRET || '',
      })

      return `/preview?${encodedParams.toString()}` // highlight-line
    },
  },
  fields: [
    {
      name: 'slug',
      type: 'text',
    },
  ],
}

Step 2: Create the Preview Route

Then, create an API route that verifies the preview secret, authenticates the user, and enters draft mode:

/app/preview/route.ts

ts
import type { PayloadRequest } from 'payload'
import { getPayload } from 'payload'

import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

import configPromise from '@payload-config'

export async function GET(
  req: {
    cookies: {
      get: (name: string) => {
        value: string
      }
    }
  } & Request,
): Promise<Response> {
  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('You are not allowed to preview this page', {
      status: 403,
    })
  }

  if (!path) {
    return new Response('Insufficient search params', { status: 404 })
  }

  if (!path.startsWith('/')) {
    return new Response(
      'This endpoint can only be used for relative previews',
      { status: 500 },
    )
  }

  let user

  try {
    user = await payload.auth({
      req: req as unknown as PayloadRequest,
      headers: req.headers,
    })
  } catch (error) {
    payload.logger.error(
      { err: error },
      'Error verifying token for live preview',
    )
    return new Response('You are not allowed to preview this page', {
      status: 403,
    })
  }

  const draft = await draftMode()

  if (!user) {
    draft.disable()
    return new Response('You are not allowed to preview this page', {
      status: 403,
    })
  }

  // You can add additional checks here to see if the user is allowed to preview this page

  draft.enable()

  redirect(path)
}

Step 3: Query Draft Content

Finally, in your front-end application, you can detect draft mode and adjust your queries to include drafts:

/app/[slug]/page.tsx

ts
export default async function Page({ params: paramsPromise }) {
  const { slug = 'home' } = await paramsPromise

  const { isEnabled: isDraftMode } = await draftMode()

  const payload = await getPayload({ config })

  const page = await payload.find({
    collection: 'pages',
    depth: 0,
    draft: isDraftMode, // highlight-line
    limit: 1,
    overrideAccess: isDraftMode,
    where: {
      slug: {
        equals: slug,
      },
    },
  })?.then(({ docs }) => docs?.[0])

  if (page === null) {
    return notFound()
  }

  return (
    <main>
      <h1>{page?.title}</h1>
    </main>
  )
}
<Banner type="success"> **Note:** For fully working example of this, check out the official [Draft Preview Example](https://github.com/payloadcms/payload/tree/3.x/examples/draft-preview) in the [Examples Directory](https://github.com/payloadcms/payload/tree/3.x/examples). </Banner>

Conditional Preview URLs

You can also conditionally enable or disable the preview button based on the document's data. This is useful for scenarios where you only want to show the preview button when certain criteria are met.

To do this, simply return null from the preview function when you want to hide the preview button:

ts
import type { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    preview: (doc) => {
      return doc?.enabled ? `http://localhost:3000/${doc.slug}` : null
    },
  },
  fields: [
    {
      name: 'slug',
      type: 'text',
    },
    {
      name: 'enabled',
      type: 'checkbox',
    },
  ],
}