obsidian/wiki/payloadcms/versions-overview.md
2026-05-15 16:58:00 +01:00

4.8 KiB

title aliases tags sources created updated
Versions — Overview
payload-versions
payload-version-history
payload-audit-log
payloadcms
versions
drafts
autosave
audit-log
raw/versions__overview.md
2026-05-15 2026-05-15

Overview

Payload Versions track the full change history of Collections and Globals. When enabled, every save creates a new version record in a separate _slug_versions collection. The Admin UI gains diff views, version browsing, and one-click restore.

Versions are opt-in and non-destructive — they don't change the shape of your data.


Three Modes

Mode What happens on save Use case
Versions only New version created, always treated as "published" Audit log / change history (e.g. user records)
Versions + Drafts Control which version is published; drafts can be previewed before going live Content publishing workflows
Versions + Drafts + Autosave Admin UI auto-saves a draft as you type; published version unaffected Editorial workflows — never lose changes

Collection Config

// Simple — enable with defaults (max 100 versions per doc)
versions: true

// Custom
versions: {
  maxPerDoc: 50,   // 0 = unlimited; default 100
  drafts: true,    // or { autosave: true }
}

Global Config

versions: {
  max: 20,       // versions per global (no per-doc concept)
  drafts: true,
}

Database Storage

A separate collection is created per versioned collection/global:

_posts_versions      ← for 'posts' collection
_header_versions     ← for 'header' global

Each version document structure:

{
  "_id": "...",
  "parent": "...",       // parent doc ID (collections only)
  "autosave": false,
  "version": { /* full copy of the document */ },
  "createdAt": "...",
  "updatedAt": "..."
}

All fields inside version are set to not required — partial versions are valid.


Version Operations (REST / GraphQL / Local API)

REST — Collections

Method Path Description
GET /api/{slug}/versions List versions (paginated)
GET /api/{slug}/versions/:id Get single version
POST /api/{slug}/versions/:id Restore version

REST — Globals

Method Path
GET /api/globals/{slug}/versions
GET /api/globals/{slug}/versions/:id
POST /api/globals/{slug}/versions/:id

Local API — Collections

// Find versions
await payload.findVersions({ collection: 'posts', sort: '-createdAt', limit: 10 })

// Find single version
await payload.findVersionByID({ collection: 'posts', id: '...' })

// Restore
await payload.restoreVersion({ collection: 'posts', id: '...' })

Local API — Globals

await payload.findGlobalVersions({ slug: 'header' })
await payload.findGlobalVersionByID({ slug: 'header', id: '...' })
await payload.restoreGlobalVersion({ slug: 'header', id: '...' })

GraphQL

Operation Query Name
Find versions versions{CollectionPlural}
Find by ID version{CollectionSingular}
Restore restoreVersion{CollectionSingular}

Access Control

Add readVersions to your collection/global access control to gate who can view version history:

access: {
  readVersions: ({ req: { user } }) => !!user?.isAdmin,
}

This automatically restricts the Admin UI version tab as well.


Key Takeaways

  • Separate collection — versions live in _slug_versions, never polluting your main data
  • Three levels — versions-only (audit log), +drafts (publish control), +autosave (live editing safety)
  • maxPerDoc — defaults to 100; set to 0 for unlimited; tune for storage budget
  • Global versions use max (not maxPerDoc) and have no parent field
  • All versions store a full copy of the document — fields are non-required to allow partial saves
  • readVersions access function controls version history visibility independently of document read access
  • Restore via POST — any version can be promoted back to current
  • Autosave is safe — it only writes drafts, never touching the published version until you explicitly publish


Sources