Preview tokens

A preview token lets your site render the latest draft of an Object without putting a write key in the browser. The Cosmic dashboard issues the token when an editor clicks Preview. Your site passes it to the API with the Bucket read key.

How it works

  1. An editor clicks Preview on an Object in the dashboard.
  2. The dashboard calls Cosmic and receives a JWT plus a preview URL.
  3. The site loads in a split pane (or a new tab). The URL includes preview_token.
  4. Your server reads the token (query string or cosmic_preview cookie) and fetches Objects with preview_token and status=any.
  5. The token is bound to one Bucket and expires after one hour. Click Preview again to get a new one.

The dashboard session never goes to the public API. The Bucket read_key stays on your server. The preview token only unlocks draft reads.

Query parameter

Add preview_token on Object list, Object get, and revision reads:

GET https://api.cosmicjs.com/v3/buckets/:slug/objects?read_key=READ_KEY&preview_token=TOKEN&status=any

Rules:

  • Missing token: unchanged. Published by default. status=any still works with the read key alone.
  • Valid token: treated as status=any even if you omit status. Must match the Bucket (bid claim).
  • Invalid or expired token: 401. Fall back to published content.

Do not store the token on the Object type preview link. Tokens expire. The dashboard appends a fresh token each time.

SDK

import { createBucketClient } from '@cosmicjs/sdk'

const cosmic = createBucketClient({
  bucketSlug: process.env.COSMIC_BUCKET_SLUG,
  readKey: process.env.COSMIC_READ_KEY,
  previewToken, // optional, from the URL or cosmic_preview cookie
})

const { object } = await cosmic.objects
  .findOne({ type: 'posts', slug: 'hello-world' })
  .status('any')
  .props(['title', 'slug', 'metadata'])

Only pass previewToken on the server. Never put it in a client component.

Next.js helper

Sites built with Cosmic Autopilot or an agent get this injected. For an existing Next.js app:

// lib/cosmic-preview.ts
import { cookies } from 'next/headers'
import { createBucketClient } from '@cosmicjs/sdk'

export async function getCosmic(previewToken?: string) {
  const token =
    previewToken || (await cookies()).get('cosmic_preview')?.value
  const cosmic = createBucketClient({
    bucketSlug: process.env.COSMIC_BUCKET_SLUG as string,
    readKey: process.env.COSMIC_READ_KEY as string,
    ...(token ? { previewToken: token } : {}),
  })
  return { cosmic, previewToken: token }
}

Set an httpOnly cookie when preview_token is on the query string. Use SameSite=None; Secure so the cookie works inside the dashboard iframe on app.cosmicjs.com. Preview only works over HTTPS.

Allow the dashboard to frame the site. Include localhost so Preview works when the dashboard is running locally. Set this on next.config headers (Vercel), not only middleware. Do not send X-Frame-Options: DENY.

Content-Security-Policy: frame-ancestors 'self' http://localhost:3040 http://localhost:3000 https://app.cosmicjs.com https://*.cosmicjs.com

If the site blocks iframes, the dashboard still offers Open in new tab.

Set a preview link on the Object type (Bucket > Object type > Additional settings), or let an Autopilot / agent deploy set it to:

https://your-site.com/api/cosmic-preview?object_id=[object_id]

Placeholders: [object_id], [object_slug], [revision_id], [timestamp]. The dashboard adds preview_token when Preview is clicked.

See dashboard Buckets for the editor workflow.