Access control
Prebuilt Payload access functions for role checks, self-access and authenticated-only reads, plus the role utilities they are built on.
The plugin exports access functions for the common authorization patterns. They read roles from the configured role field (default role).
import type { CollectionConfig } from 'payload'
import {
hasRole,
requireAllRoles,
isAuthenticated,
isAdminField,
} from '@delmaredigital/payload-better-auth'
export const Posts: CollectionConfig = {
slug: 'posts',
access: {
read: isAuthenticated(),
create: hasRole(['editor', 'admin']),
update: hasRole(['editor', 'admin']),
delete: requireAllRoles(['admin', 'content-manager']),
},
fields: [{
name: 'internalNotes',
type: 'textarea',
access: { read: isAdminField() },
}],
}Helpers
| Helper | Kind | Grants access when |
|---|---|---|
isAdmin(config?) | Collection | The user has one of adminRoles (default ['admin']). |
isAdminField(config?) | Field | Same, for field access. |
isAdminOrSelf(config?) | Collection | Admin, or a query constraint matching the user's own record. |
canUpdateOwnFields(config?) | Collection | Admin, or the user updates their own record and only touches allowedFields. |
isAuthenticated() | Collection | Any signed-in user. |
isAuthenticatedField() | Field | Same, for field access. |
hasRole(roles, roleField?) | Collection | The user has any of roles. |
hasRoleField(roles, roleField?) | Field | Same, for field access. |
requireAllRoles(roles, roleField?) | Collection | The user has every one of roles. |
isAdmin, isAdminField, isAdminOrSelf and canUpdateOwnFields take a config object:
Prop
Type
Self-access
import { isAdmin, isAdminOrSelf, canUpdateOwnFields } from '@delmaredigital/payload-better-auth'
access: {
read: isAdminOrSelf({ adminRoles: ['admin'] }),
update: canUpdateOwnFields({
allowedFields: ['name', 'image'],
}),
delete: isAdmin({ adminRoles: ['admin'] }),
}Password changes are not allowed through canUpdateOwnFields
canUpdateOwnFields denies any update that includes password or currentPassword, even if you list them in allowedFields. Checking a password inside an access function would make every check an unthrottled login attempt. Change passwords with Better Auth's rate-limited flow instead: authClient.changePassword({ currentPassword, newPassword }).
Role utilities
import { normalizeRoles, hasAnyRole, hasAllRoles } from '@delmaredigital/payload-better-auth'
const roles = normalizeRoles(user.role) // always string[]
hasAnyRole(user, ['admin', 'editor'])
hasAllRoles(user, ['admin', 'editor'])
hasAnyRole(user, ['admin'], 'roles') // custom role fieldnormalizeRoles treats a string as a single role. It does not split on commas, so store multiple roles as an array.
For API-key scopes in access control, see API keys.

