Payload CMS uses a code-first access control system where permissions are defined in TypeScript/JavaScript collection configs rather than through an admin UI. Every collection and global can have granular access functions that evaluate at runtime, receiving the authenticated user and the document being accessed. This gives Payload one of the most flexible permission models in the headless CMS space -- but it means permissions live in code, not in a database-driven role matrix.
Permission model
Payload's access control operates at four levels:
- Collection-level access --
create,read,update,deletefunctions on each collection. Returntrue(allow all),false(deny all), or a query filter (allow matching documents only). - Field-level access -- individual fields can have their own
access.read,access.create,access.updatefunctions that override collection-level settings for that specific field. - Global-level access -- Globals (singleton documents like site settings) have
readandupdateaccess functions. - Admin panel access -- the
adminproperty on auth-enabled collections controls who can log into the Payload admin UI.
Access functions receive ({ req, id, data }) and can implement any logic: role checks, ownership verification, time-based access, IP restrictions, or external service calls.
Defining roles
Payload does not have built-in named roles. You define roles as values on a field in your auth collection:
// collections/Users.ts
const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
access: {
admin: ({ req: { user } }) => user?.role === 'admin',
},
fields: [
{
name: 'role',
type: 'select',
options: ['admin', 'editor', 'analyst', 'viewer'],
required: true,
defaultValue: 'viewer',
},
],
};
Common role patterns:
| Role value | Typical access | Implementation pattern |
|---|---|---|
admin |
Full CRUD on all collections, admin panel access | access: () => true on all collections |
editor |
Create/update content, no settings or user management | Collection access returns true for content, false for settings |
analyst |
Read reports and analytics data, no content editing | read: () => true, create/update/delete: () => false |
viewer |
Read-only access to published content | read: ({ req }) => ({ _status: { equals: 'published' } }) |
api-client |
Programmatic access via API keys | Auth strategy using API key header, scoped access functions |
Admin UI paths
Since Payload is code-configured, "paths" refer to both admin panel routes and config file locations:
| Task | Location |
|---|---|
| Manage users | Admin panel > Users collection (or your auth collection slug) |
| Define roles | collections/Users.ts > fields array |
| Set collection permissions | collections/[Name].ts > access property |
| Set field permissions | fields array > individual field access property |
| Configure auth strategy | collections/Users.ts > auth property |
| API key auth | auth: { useAPIKey: true } in collection config |
| Admin panel access | access.admin function in auth collection |
API access management
Payload exposes REST and GraphQL APIs automatically for every collection:
REST API:
- Endpoints at
/api/[collection-slug]for CRUD operations - Authentication via cookie session, JWT Bearer token, or API key header
- All access functions apply identically to REST and admin panel requests
GraphQL API:
- Auto-generated schema at
/api/graphql - Same access control as REST -- field-level access hides fields from the schema for unauthorized users
- GraphQL introspection can be disabled in production
API Keys:
- Enable with
auth: { useAPIKey: true }on the auth collection - Each user gets an auto-generated API key visible in their profile
- Pass via
Authorization: users API-Key <key>header - Keys inherit the user's role and all associated access functions
Auth strategies (Payload 3.x):
- Custom auth strategies for OAuth, SAML, LDAP
- Configure in
payload.config.tsundercollections[].auth.strategies - Each strategy maps external identity claims to a local user record
Analytics-specific permissions
Payload's code-first model makes analytics permissions precise:
- Analytics data collection -- if you store analytics events in a Payload collection, set
access.readto allow analysts andaccess.createto allow only your ingestion service (check API key or IP). - Dashboard globals -- store analytics dashboard configuration in a Global with
access.updaterestricted to admins andaccess.readopen to analysts. - Tag management fields -- add tracking IDs (GA Measurement ID, GTM Container ID) as fields on a Settings global. Use field-level access to let marketing update tracking IDs without accessing other settings:
{
name: 'gaMeasurementId',
type: 'text',
access: {
update: ({ req }) => ['admin', 'analyst'].includes(req.user?.role),
},
}
- Content preview with analytics -- Payload's live preview feature can embed analytics dashboards. Control preview access via the
admin.previewfunction. - Audit logging -- use Payload's
afterChangeandafterDeletehooks to log who modified analytics-related collections. Payload 3.x also supports theversionsfeature for full document history.
Sub-pages
- Roles and Permissions -- access control function patterns, field-level permissions, and multi-tenant configurations
- Adding and Removing Users -- user provisioning via admin panel and API, auth strategies, and deactivation patterns