obsidian/wiki/tech-patterns/payload-cms-collection-access-control.md
2026-05-15 14:40:01 +01:00

4.7 KiB

title aliases tags sources created updated
Payload CMS — Collection Access Control
payload-access-control
payload-collections-permissions
payload-cms
access-control
permissions
nextjs
cms
raw/access-control__collections.md
2026-05-15 2026-05-15

Payload CMS — Collection Access Control

Defines which users can create, read, update, or delete documents within a wiki/tech-patterns/payload-cms-installation. Controlled via the access property on CollectionConfig.

Config Structure

import type { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',
  access: {
    create:       ({ req: { user }, data }) => Boolean(user),
    read:         ({ req: { user } }) => Boolean(user),
    update:       ({ req: { user } }) => Boolean(user),
    delete:       ({ req: { user } }) => Boolean(user),
    // Auth-enabled collections only:
    admin:        ({ req: { user } }) => Boolean(user),
    unlock:       ({ req: { user } }) => Boolean(user),
    // Versions-enabled collections only:
    readVersions: ({ req: { user } }) => Boolean(user),
  },
}

Access Functions Reference

Function Operation Extra args
create POST /api/:collection req, data
read GET /api/:collection, findByID req, id
update PATCH /api/:collection/:id req, id, data
delete DELETE /api/:collection/:id req, id, data
admin Admin Panel access req
unlock Unlock locked-out users req
readVersions Version history tab req

All functions receive req which contains req.user (the authenticated user or null).

Return Values

Each function can return:

  • true — full access granted
  • false — access denied
  • Query constraint — access limited to documents matching the where-clause (works for read, update, delete, readVersions)

Common Patterns

Public read, authenticated write

read: ({ req: { user } }) => {
  if (user) return true
  return { isPublic: { equals: true } }  // guest users see only public docs
},
create: ({ req: { user } }) => Boolean(user),

Role-based update

update: ({ req: { user }, id }) => {
  if (user?.roles?.includes('admin')) return true
  return user?.id === id  // users can only update themselves
},

Delete guard — check foreign references

delete: async ({ req, id }) => {
  if (!id) return true  // let Admin UI show controls without id
  const result = await req.payload.find({
    collection: 'contracts',
    where: { customer: { equals: id } },
    limit: 0, depth: 0,
  })
  return result.totalDocs === 0  // block delete if contracts exist
},

Soft-delete vs hard-delete (Trash-enabled)

delete: ({ req: { user }, data }) => {
  const isSoftDelete = Boolean(data?.deletedAt)
  if (isSoftDelete) return Boolean(user)       // any auth user can trash
  return user?.roles?.includes('admin') ?? false  // only admins hard-delete
},

Tips & Gotchas

  • Returning a Query on readVersions applies the constraint to the _versions collection, not the original — be aware of field name differences.
  • Draft publishing control: use update returning a query on _status field to restrict who can publish drafts.
  • delete without id (bulk/Admin UI list view) — id is undefined; returning true lets the UI render delete controls even before knowing which document is targeted.
  • Extract complex functions into a separate access/ file to keep CollectionConfig readable. Import with import type { Access } from 'payload'.
  • admin access only applies to Auth-enabled collections (the collection used as the admin user source).

Key Takeaways

  • Access is per-operation — create, read, update, delete are always available; admin/unlock require Auth; readVersions requires Versions.
  • Return true/false for binary access or a Query object for row-level security.
  • req.user is null for unauthenticated requests — always guard with Boolean(user) or a null-check.
  • Complex access logic belongs in a separate file using the Access<CollectionType> generic type.
  • delete receives data.deletedAt for Trash-enabled collections — use it to distinguish soft vs permanent deletes.

Sources