Skip to content

delmaredigital/payload-better-auth

Repository files navigation

@delmaredigital/payload-better-auth

Better Auth adapter and plugins for Payload CMS. Enables seamless integration between Better Auth and Payload.

Live Demo - Try It Now    Starter Template - Use This

Deploy with Vercel

⚠️ Upgrading to 0.7? This release requires Better Auth 1.6 and includes several breaking changes:

  • Schema migration required for projects using the twoFactor plugin — Better Auth 1.6.2 added a verified column to the twoFactor table.
  • oidcProvider@better-auth/oauth-provider — generated OAuth types now reflect the oauth-provider schema. Consumers using oidcProvider() at runtime will keep working, but OauthApplication / PluginId / ModelKey type exports have changed shape. Migrating to @better-auth/oauth-provider is recommended.
  • Client helper type wideningcreatePayloadAuthClient() and payloadAuthPlugins are typed more conservatively to keep .d.ts portable. For typed plugin methods (e.g. client.twoFactor.verifyTotp), list plugins explicitly in createAuthClient({ plugins: [...] }) (see Client-Side Auth below).

See the CHANGELOG for full migration instructions.


Documentation

Full Documentation — API reference, guides, recipes, UI components, and more.

For AI-assisted exploration: DeepWiki


Install

pnpm add @delmaredigital/payload-better-auth better-auth

Requirements: payload >= 3.69.0 · better-auth >= 1.6.0 · next >= 15.4.8 · react >= 19.2.1

Quick Start

1. Auth Configuration

// 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 },
}

2. Users Collection

// 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' },
      ],
    },
  ],
}

3. Payload Config

// 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 },
  }),
})

4. Client-Side Auth

// 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 } = authClient

Listing plugins inline (rather than using createPayloadAuthClient() or spreading payloadAuthPlugins) ensures twoFactor and other plugin methods are typed on the returned client.

5. Server-Side Session

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.

License

MIT