Quick Setup Walkthrough

End-to-end setup, from install to a protected Next.js route, with the most common config knobs in one place

Quick Setup Walkthrough

A pragmatic, top-to-bottom walkthrough that wires the plugin into a Payload + Next.js app, sets up the most common knobs (email flows, social providers, custom roles, a custom user collection), and ends with a protected route. Each section links to the deeper reference page.

Prerequisites

  • Node.js 20.9+ (the floor required by Next.js 16)
  • Next.js 16.2.2 or newer
  • Payload v3 on any supported database
  • React 19 (App Router)

1. Install

npm install @b3nab/payload-better-auth

better-auth is a direct dependency - no separate install.

2. Create the plugin config in its own file

Keeping the config separate from payload.config.ts keeps things readable and gives you good type inference. Use satisfies BetterAuthPluginOptions so TypeScript validates the shape without widening literal types.

@/lib/payload-better-auth.config.ts
import type { BetterAuthPluginOptions } from '@b3nab/payload-better-auth'

export const payloadBetterAuthConfig = {
  logs: 'info',
  betterAuth: {
    appName: 'My App',
    baseURL: process.env.NEXT_PUBLIC_SERVER_URL,
    trustedOrigins: [process.env.NEXT_PUBLIC_SERVER_URL!],
  },
} satisfies BetterAuthPluginOptions

Even an empty {} works - the plugin ships sensible defaults (admin, two-factor, openAPI in dev, next-cookies, default email flows). Add only what you want to change.

3. Wire it into Payload

@/payload.config.ts
import { buildConfig } from 'payload'
import { betterAuthPlugin } from '@b3nab/payload-better-auth'
import { payloadBetterAuthConfig } from '@/lib/payload-better-auth.config'

export default buildConfig({
  // ...your existing config
  plugins: [betterAuthPlugin(payloadBetterAuthConfig)],
})

4. Environment variables

.env
BETTER_AUTH_SECRET=  # openssl rand -base64 32
NEXT_PUBLIC_SERVER_URL=http://localhost:3000

5. Customize the user collection

Treat the user collection like any Payload collection: define it in its own file, type it with CollectionConfigExtend<'user'>, then plug it in.

@/collections/User.ts
import type { CollectionConfigExtend } from '@b3nab/payload-better-auth'

export const User: CollectionConfigExtend<'user'> = {
  admin: { useAsTitle: 'email', group: 'My App' },
  fields: [
    { name: 'nickname', type: 'text' },
    {
      name: 'posts',
      type: 'relationship',
      relationTo: 'posts',
      hasMany: true,
    },
  ],
  hooks: {
    afterChange: [
      ({ doc }) => {
        // anything Payload supports
      },
    ],
  },
}
@/lib/payload-better-auth.config.ts (updated)
import { User } from '@/collections/User'

export const payloadBetterAuthConfig = {
  extendsCollections: { user: User },
  betterAuth: { /* ... */ },
} satisfies BetterAuthPluginOptions

Need a field that should also be visible to Better Auth's typed APIs (e.g. on session.user)? Add it through betterAuth.user.additionalFields instead - both paths land in the same Payload collection. See Collections.

6. Add social providers

Configure providers under betterAuth.socialProviders. The plugin auto-injects social login buttons into the Payload admin login screen.

betterAuth: {
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
}

See Social Login.

7. Add custom roles

Pass your own admin() instance with extended roles via betterAuth.plugins. Reuse the exported ac so your roles speak the same access-control vocabulary as the built-ins.

@/lib/permissions.ts
import { ac, roles } from '@b3nab/payload-better-auth'

const editor = ac.newRole({
  payloadcms: ['access'],   // can enter the admin
  byRole: ['user'],
})

export const acBV = ac
export const rolesBV = { ...roles, editor }
@/lib/payload-better-auth.config.ts (updated)
import { admin } from 'better-auth/plugins/admin'
import { acBV, rolesBV } from '@/lib/permissions'

export const payloadBetterAuthConfig = {
  // ...
  betterAuth: {
    plugins: [
      admin({ ac: acBV, roles: rolesBV }),
    ],
    // ...
  },
} satisfies BetterAuthPluginOptions

See Permissions.

8. Customize email flows

Defaults are already wired to payload.sendEmail(...). Override any handler when you want full control:

betterAuth: {
  emailVerification: {
    sendOnSignUp: true,
    autoSignInAfterVerification: true,
    sendVerificationEmail: async ({ user, url }) => {
      await myMailer.send({
        to: user.email,
        subject: 'Verify your email',
        html: renderTemplate('verify', { url }),
      })
    },
  },
  emailAndPassword: {
    enabled: true,
    sendResetPassword: async ({ user, url }) => {
      await myMailer.send({
        to: user.email,
        subject: 'Reset your password',
        html: renderTemplate('reset', { url }),
      })
    },
  },
}

See Email Flows.

9. Create the auth layer for Next.js

@/lib/auth.ts
import { createAuthLayer } from '@b3nab/payload-better-auth'
import config from '@/payload.config'
import { payloadBetterAuthConfig } from '@/lib/payload-better-auth.config'

export const {
  auth,
  // checkers
  isAuth, isGuest, isUser, isAdmin, isRole,
  // guards
  guardAuth, guardGuest, guardUser, guardAdmin, guardRole,
} = createAuthLayer(config, payloadBetterAuthConfig)

auth is the underlying Better Auth instance, typed through InferBetterAuthInstance<O>. Any plugin you add under betterAuth.plugins (custom roles, 2FA, social providers, etc.) is reflected in the auth.api.* typings out of the box.

10. Protect a route

@/app/admin-area/page.tsx
import { guardAdmin } from '@/lib/auth'

export default async function AdminAreaPage() {
  const { user } = await guardAdmin('/login') // bounces if not admin
  return <div>Hello {user.email}</div>
}
@/app/dashboard/page.tsx
import { guardRole } from '@/lib/auth'

export default async function DashboardPage() {
  const { user } = await guardRole({ role: 'editor' }, '/login')
  return <div>Welcome, {user.nickname ?? user.email}</div>
}

See Guards and Checkers.

11. Run

pnpm dev

Open the admin at http://localhost:3000/admin, register the first user (becomes admin), and verify:

  • Sign-up/login work and the verification email lands.
  • Social login buttons render on /admin/login if you configured providers.
  • Visiting /admin/two-factor-setup lets you enrol TOTP.
  • Your protected Next.js routes redirect when unauthenticated and render when authorized.

Where to go next

On this page