obsidian/wiki/payloadcms/versions-drafts.md
2026-05-15 16:56:10 +01:00

5.2 KiB

title aliases tags sources created updated
Versions — Drafts
payload-drafts
draft-publish
payload-draft-api
payloadcms
versions
drafts
publish
access-control
raw/versions__drafts.md
2026-05-15 2026-05-15

Overview

Drafts build on wiki/payloadcms/versions-autosave to let you save changes without publishing them immediately. Requires versions to be enabled first.

// Collection or Global
versions: {
  drafts: {
    autosave: true,            // or pass autosave options object
    schedulePublish: true,     // allow future publish/unpublish events
    validate: false,           // validate on draft save (default: false)
    localizeStatus: false,     // Beta: localize _status field
  },
}

Database Changes

Enabling drafts injects a _status field ('draft' | 'published') into your schema automatically.

Admin UI statuses:

  • Draft — never published; only draft versions exist
  • Published — published, no newer drafts
  • Changed — published, but newer unpublished drafts exist

How draft Param and _status Work Together

Operation draft param _status in data Result
Create true or false omitted Main collection updated with _status: 'draft'
Create true or false 'published' Main collection updated with _status: 'published'
Update true omitted or 'draft' Only versions table updated; main collection unchanged
Update true 'published' Main collection updated with _status: 'published' (override)
Update false or omitted omitted Main collection updated with _status: 'draft'
Update false or omitted 'published' Main collection updated with _status: 'published'

Key distinctions:

  • draft: true → skips required field validation + writes only to versions table
  • _status: 'published' → overrides draft: true and updates the main collection
  • Setting _status: 'draft' alone does not skip required field validation; use draft: true for that

Draft API Examples

// Save as draft (only versions table updated)
await payload.update({
  collection: 'pages',
  id,
  data: { title: 'WIP' },
  draft: true,
})

// Publish (main collection updated)
await payload.update({
  collection: 'pages',
  id,
  data: { _status: 'published' },
})

// Read latest version (draft or published)
await payload.findByID({
  collection: 'pages',
  id,
  draft: true,   // returns newest version from versions table
})

REST equivalent: POST /api/pages?draft=true

Controlling Who Can Read Drafts

Critical: draft: true on read does NOT filter by _status. Use Access Control.

access: {
  read: ({ req }) => {
    if (req.user) return true         // logged-in: see everything
    return { _status: { equals: 'published' } }  // guests: published only
  },
}

Migration gotcha: existing documents before drafts was enabled won't have _status. Allow both cases:

return {
  or: [
    { _status: { equals: 'published' } },
    { _status: { exists: false } },
  ],
}

Controlling Who Can Publish

Use update access control with a query constraint on _status:

access: {
  update: ({ req: { user } }) => {
    if (!user) return false
    if (user.role === 'admin') return true
    // editors can only update drafts (not published docs)
    return { _status: { equals: 'draft' } }
  },
}

This also suppresses the Publish/Unpublish buttons in Admin UI for non-admins.

Scheduled Publish

Enable with versions.drafts.schedulePublish: true. Requires wiki/payloadcms/jobs-queue to be running — scheduled events are queued as background jobs.

Unpublishing & Reverting

  • Unpublish → sets _status: 'draft' on the main collection document
  • Revert to published → creates a new version reflecting last published state (draft history is preserved)

Key Takeaways

  • draft: true controls validation bypass + write location (versions table only)
  • _status: 'published' controls publish state — always overrides draft: true
  • First document creation always writes to main collection regardless of draft param
  • Read with draft: true returns the newest version (could be draft or published)
  • Access Control is required to hide drafts from unauthenticated users
  • schedulePublish requires the Jobs Queue to be configured and running
  • Adding drafts to an existing collection → old docs have no _status; update AC accordingly

Sources