payload-auth
Reference

Plugin options

Every option accepted by betterAuthPlugin().

betterAuthPlugin(options: PayloadAuthOptions) returns a Payload config transformer.

import { betterAuthPlugin, type PayloadAuthOptions } from 'payload-auth/better-auth'

Top level

Prop

Type

admin.loginMethods

Valid values:

emailPassword, magicLink, emailOTP, phonePassword, phoneOTP, phoneMagicLink, passkey, and the social providers apple, discord, facebook, github, google, linkedin, microsoft, spotify, tiktok, twitter, twitch, zoom, gitlab, roblox, vk, kick, reddit.

If you leave it unset, the plugin infers the list from socialProviders, emailAndPassword.enabled and the presence of the passkey plugin.

pluginCollectionOverrides keys

subscriptions, apiKeys, jwks, twoFactors, passkeys, oauthApplications, oauthAccessTokens, oauthConsents, ssoProviders, organizations, organizationRoles, invitations, members, teams, teamMembers, scimProvider, rateLimit, deviceCode.

Runtime-validated only

TypeScript cannot tell which Better Auth plugins you enabled, so it will let you configure a collection that does not exist. That is validated at runtime.

users

Prop

Type

accounts, sessions, verifications

All three take the same shape:

Prop

Type

adminInvitations

Prop

Type

betterAuthOptions

This is Better Auth's own options object with database removed — the plugin supplies the adapter. Everything else behaves as the Better Auth options reference describes.

The plugin rewrites parts of it before handing it to Better Auth:

  • user, session, account and verification modelName are set to your Payload slugs.
  • session.fields.userId and account.fields.userId are pointed at the user relationship.
  • emailAndPassword.enabled defaults to true.
  • Supported plugins get their modelName and field mappings configured.
  • saveToJWT: false on Payload fields is mirrored onto the returned-field set, so excluded fields never reach the cookie cache.

A worked example

src/lib/auth/options.ts
import type { BetterAuthOptions, PayloadAuthOptions } from 'payload-auth/better-auth'
import { admin, organization, twoFactor } from 'better-auth/plugins'
import { passkey } from '@better-auth/passkey'
import { nextCookies } from 'better-auth/next-js'

export const betterAuthOptions = {
  appName: 'my-app',
  baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL,
  trustedOrigins: [process.env.NEXT_PUBLIC_BETTER_AUTH_URL!],
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    async sendResetPassword({ user, url }) {
      await sendEmail({ to: user.email, subject: 'Reset your password', html: url })
    },
  },
  emailVerification: {
    sendOnSignUp: true,
    autoSignInAfterVerification: true,
    async sendVerificationEmail({ user, url }) {
      await sendEmail({ to: user.email, subject: 'Verify your email', html: url })
    },
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    },
  },
  session: {
    cookieCache: { enabled: true, maxAge: 5 * 60 },
  },
  account: {
    accountLinking: { enabled: true, trustedProviders: ['google'] },
  },
  plugins: [
    admin(),
    twoFactor({ issuer: 'my-app' }),
    passkey({ rpID: 'localhost', rpName: 'My App', origin: 'http://localhost:3000' }),
    organization({ teams: { enabled: true } }),
    nextCookies(), // keep last
  ],
} satisfies BetterAuthOptions

export const payloadAuthOptions = {
  hidePluginCollections: true,
  collectionAdminGroup: 'Auth',
  users: {
    slug: 'users',
    roles: ['user', 'editor', 'admin'],
    adminRoles: ['admin'],
    defaultRole: 'user',
    defaultAdminRole: 'admin',
    allowedFields: ['name', 'image'],
  },
  adminInvitations: {
    async sendInviteEmail({ payload, email, url }) {
      await payload.sendEmail({ to: email, subject: 'You are invited', html: url })
      return { success: true }
    },
  },
  betterAuthOptions,
} satisfies PayloadAuthOptions

export type PayloadAuthConfig = typeof payloadAuthOptions

Using satisfies rather than : preserves the literal types, which is what makes getPayloadAuth<PayloadAuthConfig>() infer your plugin set and role union correctly.

On this page