Skip to Content
Delmare Digital

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).

src/collections/Posts.ts
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

HelperKindGrants access when
isAdmin(config?)CollectionThe user has one of adminRoles (default ['admin']).
isAdminField(config?)FieldSame, for field access.
isAdminOrSelf(config?)CollectionAdmin, or a query constraint matching the user's own record.
canUpdateOwnFields(config?)CollectionAdmin, or the user updates their own record and only touches allowedFields.
isAuthenticated()CollectionAny signed-in user.
isAuthenticatedField()FieldSame, for field access.
hasRole(roles, roleField?)CollectionThe user has any of roles.
hasRoleField(roles, roleField?)FieldSame, for field access.
requireAllRoles(roles, roleField?)CollectionThe user has every one of roles.

isAdmin, isAdminField, isAdminOrSelf and canUpdateOwnFields take a config object:

Prop

Type

Self-access

src/collections/Users/index.ts
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 field

normalizeRoles 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.

Edit this page on GitHub

On this page