API keys
Enable Better Auth API keys, manage them from the Payload admin, and enforce per-collection read and write scopes in access control.
API keys come from Better Auth's @better-auth/api-key plugin. Once it is configured, the plugin adds an API key management view to the admin and gives you access functions that check a key's scopes.
Setup
npm i @better-auth/api-keyimport { apiKey } from '@better-auth/api-key'
export const betterAuthOptions = {
plugins: [
apiKey(),
],
}The management UI and the permission helpers work once the apiKey plugin is configured. The admin view appears when the plugin can see your Better Auth options; see Security management.
API-key security parameters (expiry, rate limit, key length) are yours to set on apiKey(), as in any Better Auth setup. The plugin does not impose defaults.
Sending a key
Send keys in the x-api-key header. That is what Better Auth's api-key plugin reads by default. Authorization: Bearer <key> is only treated as an API key if you configure the plugin's apiKeyHeaders or customAPIKeyGetter; otherwise such a request authenticates as nobody.
Permissions model
Each collection has two permission levels: read (view only) and write (create, update and delete). write implies read. Permissions use Better Auth's native format, Record<string, string[]> keyed by collection slug, and the admin UI generates the choices from your Payload collections.
Configure the management UI under admin.apiKey on createBetterAuthPlugin():
createBetterAuthPlugin({
createAuth,
admin: {
apiKey: {
excludeCollections: ['internal-logs'],
requiredRole: 'admin',
},
},
})Prop
Type
The default exclusions do not match the generated API-key collection slug (apikeys, or apikey with usePlural: false). To hide it from the permission picker, pass an explicit excludeCollections list containing the actual slug and every other collection you want excluded. An explicit list replaces the defaults.
Enforcing permissions
Use these in Payload access control. They prefer the key's scopes that betterAuthStrategy already resolved onto req.user (req.user.apiKeyScopes), which does not spend the key's usage quota or a rate-limit slot. When those are not available, they verify the key with Better Auth at most once per request, however many permissions a check asks for.
import {
requirePermission,
allowSessionOrPermission,
} from '@delmaredigital/payload-better-auth'
access: {
read: allowSessionOrPermission('posts', 'read'), // session users or keys with posts:read
create: requirePermission('posts', 'write'),
update: requirePermission('posts', 'write'),
delete: requirePermission('posts', 'write'),
}| Helper | Grants access when |
|---|---|
requirePermission(resource, action, config?) | The request carries a key with that permission. |
requireAnyPermission(checks, config?) | The key has any of the { resource, action } checks. |
requireAllPermissions(checks, config?) | The key has all of them. |
allowSessionOrPermission(resource, action, config?) | A signed-in (non-key) session, or a key with that permission. |
allowSessionOrAnyPermission(checks, config?) | A signed-in session, or a key with any of the checks. |
requireApiKey(config?) | Any valid key, with no specific permission. |
import { requireAnyPermission, requireAllPermissions, requireApiKey } from '@delmaredigital/payload-better-auth'
// Require ANY of these permissions
read: requireAnyPermission([
{ resource: 'posts', action: 'read' },
{ resource: 'pages', action: 'read' },
])
// Require ALL permissions
delete: requireAllPermissions([
{ resource: 'posts', action: 'write' },
{ resource: 'admin', action: 'write' },
])
// Just verify the key is valid (no specific permissions)
read: requireApiKey()The optional config takes allowAuthenticatedUsers (default false; the allowSession… helpers set it to true) and extractApiKey, a function that returns the key from the request. The default, extractApiKeyFromRequest, reads x-api-key and Authorization.
A user resolved from an API key never counts as a session user, so a scoped or zero-scope key cannot pass an allowSession… check by being "authenticated".
Access control
Prebuilt Payload access functions for role checks, self-access and authenticated-only reads, plus the role utilities they are built on.
Plugin compatibility
Which Better Auth plugins work with the Payload adapter, which collections they generate, and how to add joins and cascade deletes.

