Better Auth adapter and plugins for Payload CMS. Enables seamless integration between Better Auth and Payload.
⚠️ Upgrading to 0.7? This release requires Better Auth 1.6 and includes several breaking changes:
- Schema migration required for projects using the
twoFactorplugin — Better Auth 1.6.2 added averifiedcolumn to thetwoFactortable.oidcProvider→@better-auth/oauth-provider— generated OAuth types now reflect theoauth-providerschema. Consumers usingoidcProvider()at runtime will keep working, butOauthApplication/PluginId/ModelKeytype exports have changed shape. Migrating to@better-auth/oauth-provideris recommended.- Client helper type widening —
createPayloadAuthClient()andpayloadAuthPluginsare typed more conservatively to keep.d.tsportable. For typed plugin methods (e.g.client.twoFactor.verifyTotp), list plugins explicitly increateAuthClient({ plugins: [...] })(see Client-Side Auth below).See the CHANGELOG for full migration instructions.
Full Documentation — API reference, guides, recipes, UI components, and more.
For AI-assisted exploration: DeepWiki
pnpm add @delmaredigital/payload-better-auth better-authRequirements: payload >= 3.69.0 · better-auth >= 1.6.0 · next >= 15.4.8 · react >= 19.2.1
// src/lib/auth/config.ts
import type { BetterAuthOptions } from 'better-auth'
export const betterAuthOptions: Partial<BetterAuthOptions> = {
user: {
additionalFields: {
role: { type: 'string', defaultValue: 'user' },
},
},
emailAndPassword: { enabled: true },
}// src/collections/Users/index.ts
import type { CollectionConfig } from 'payload'
import { betterAuthStrategy } from '@delmaredigital/payload-better-auth'
export const Users: CollectionConfig = {
slug: 'users',
auth: {
disableLocalStrategy: true,
strategies: [betterAuthStrategy()],
},
access: {
read: ({ req }) => {
if (!req.user) return false
if (req.user.role === 'admin') return true
return { id: { equals: req.user.id } }
},
admin: ({ req }) => req.user?.role === 'admin',
},
fields: [
{ name: 'email', type: 'email', required: true, unique: true },
{ name: 'emailVerified', type: 'checkbox', defaultValue: false },
{ name: 'name', type: 'text' },
{ name: 'image', type: 'text' },
{
name: 'role',
type: 'select',
defaultValue: 'user',
options: [
{ label: 'User', value: 'user' },
{ label: 'Admin', value: 'admin' },
],
},
],
}// src/payload.config.ts
import { buildConfig } from 'payload'
import { postgresAdapter } from '@payloadcms/db-postgres'
import { betterAuth } from 'better-auth'
import {
betterAuthCollections,
createBetterAuthPlugin,
payloadAdapter,
} from '@delmaredigital/payload-better-auth'
import { betterAuthOptions } from './lib/auth/config'
import { Users } from './collections/Users'
import { getBaseUrl } from './lib/auth/getBaseUrl'
const baseUrl = getBaseUrl()
export default buildConfig({
collections: [Users],
plugins: [
betterAuthCollections({
betterAuthOptions,
skipCollections: ['user'],
}),
createBetterAuthPlugin({
createAuth: (payload) =>
betterAuth({
...betterAuthOptions,
database: payloadAdapter({ payloadClient: payload }),
advanced: { database: { generateId: 'serial' } },
baseURL: baseUrl,
secret: process.env.BETTER_AUTH_SECRET,
trustedOrigins: [baseUrl],
}),
}),
],
db: postgresAdapter({
pool: { connectionString: process.env.DATABASE_URL },
}),
})// src/lib/auth/client.ts
'use client'
import { createAuthClient, twoFactorClient } from '@delmaredigital/payload-better-auth/client'
import { passkeyClient } from '@better-auth/passkey/client'
export const authClient = createAuthClient({
plugins: [twoFactorClient(), passkeyClient()],
})
export const { useSession, signIn, signUp, signOut, twoFactor, passkey } = authClientListing plugins inline (rather than using
createPayloadAuthClient()or spreadingpayloadAuthPlugins) ensurestwoFactorand other plugin methods are typed on the returned client.
import { headers } from 'next/headers'
import { getPayload } from 'payload'
import { getServerSession } from '@delmaredigital/payload-better-auth'
export default async function Dashboard() {
const payload = await getPayload({ config })
const headersList = await headers()
const session = await getServerSession(payload, headersList)
if (!session) { redirect('/login') }
return <div>Hello {session.user.name}</div>
}That's it! The plugin automatically registers auth API endpoints at /api/auth/*, injects admin UI components, and handles session management.
For MongoDB setup, API reference, customization, access control helpers, API key scopes, plugin compatibility, UI components (2FA, passkeys, password reset), recipes, and types — see the full documentation.
MIT