obsidian/wiki/tech-patterns/payload-cms-access-control-overview.md
2026-05-15 15:13:56 +01:00

2.9 KiB

title aliases tags sources created updated
Payload CMS — Access Control Overview
payload-access-control
payload-cms-permissions
payload-cms
access-control
rbac
permissions
cms
raw/access-control__overview.md
2026-05-15 2026-05-15

Payload CMS — Access Control Overview

Access Control in Payload determines what a user can do with any Document and what they see in the Admin Panel. Functions are scoped per operation and run before any changes are made.

Three Types

Type Article Scope
Collection wiki/tech-patterns/payload-cms-collection-access-control Per-operation on collection documents
Global wiki/tech-patterns/payload-cms-globals-access-control Per-operation on singleton globals
Field wiki/tech-patterns/payload-cms-field-access-control Per-field within a document

Default Access Control

Payload ships with a default that requires an authenticated user:

const defaultPayloadAccess = ({ req: { user } }) => Boolean(user)
  • Local API skips access control by default — set overrideAccess: false to opt back in.

Access Operation & Admin Panel Sync

  • On login, Payload runs every access function at the top level (all Collections, Globals, Fields) to determine what the user can do.
  • Admin Panel hides/shows UI elements dynamically based on this result.
  • When called via the Access Operation, id, data, siblingData, blockData, and doc are all undefined — always guard against this before using them.
const access = ({ req: { user }, id }) => {
  if (!id) return Boolean(user) // Access Operation context
  return user?.role === 'admin'
}

Locale-Specific Access Control

Use req.locale to restrict access per locale:

const access = ({ req }) => req.locale === 'en'

Common Patterns

  • Public read: return true from read
  • Published only: return { status: { equals: 'published' } } (Query constraint)
  • Owner only: return { createdBy: { equals: req.user.id } }
  • Admin role: return req.user?.role === 'admin'
  • Org-scoped: return { organization: { equals: req.user.orgId } }

Key Takeaways

  • Access functions execute before operations — they are guards, not post-filters
  • Return true/false for full allow/deny; return a Where query for row-level security (Collections/Globals only — Fields are boolean-only)
  • Local API bypasses access control unless overrideAccess: false is set
  • Admin Panel UI reflects access control automatically via the Access Operation
  • Guard id/data/doc for undefined inside access functions — they are absent when called by the Access Operation
  • Locale-aware access is possible via req.locale

Sources