payload-auth
Guides

Email and password

Enable email/password sign-in, email verification and password resets.

Email and password authentication is enabled by default. If you set no emailAndPassword block at all, the plugin turns it on so the admin panel has a way in.

Configuration

src/lib/auth/options.ts
export const betterAuthOptions = {
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    autoSignIn: false,
    minPasswordLength: 8,
    async sendResetPassword({ user, url, token }) {
      await sendEmail({
        to: user.email,
        subject: 'Reset your password',
        html: `<a href="${url}">Reset your password</a>`,
      })
    },
  },
} satisfies BetterAuthOptions

To turn it off — a panel that only accepts social login or passkeys — set enabled: false explicitly.

Email verification

export const betterAuthOptions = {
  emailVerification: {
    sendOnSignUp: true,
    autoSignInAfterVerification: true,
    async sendVerificationEmail({ user, url }) {
      await sendEmail({
        to: user.email,
        subject: 'Verify your email',
        html: `<a href="${url}">Verify your email</a>`,
      })
    },
  },
} satisfies BetterAuthOptions

The verification state lands in the emailVerified checkbox on the users collection, so you can see and (as an admin) correct it from the panel.

Developing without an email transport

Log the URL instead of sending it and paste it into your browser. See Create your first admin.

Sending real email

Better Auth's callbacks are the place to send mail — they receive the recipient and the signed URL, and it is up to you how to deliver it. If you already configured Payload's email adapter, reuse it:

src/lib/auth/options.ts
import { getPayload } from '@/lib/payload'

async function sendVerificationEmail({ user, url }) {
  const payload = await getPayload()
  await payload.sendEmail({
    to: user.email,
    subject: 'Verify your email',
    html: `<a href="${url}">Verify your email</a>`,
  })
}

Password resets from the admin panel

/admin/forgot-password and /admin/reset-password are wired to Better Auth's reset flow. The forgot-password form calls Better Auth, which invokes your emailAndPassword.sendResetPassword callback. If you have not implemented that callback, the form appears to succeed but no email is sent.

Changing a password

Users change their own password through the Better Auth client:

await authClient.changePassword({
  currentPassword,
  newPassword,
  revokeOtherSessions: true,
})

Through Payload's API instead, send both currentPassword and password — the plugin verifies the current password before permitting the update. See Roles and access control.

OptionEffect
requireEmailVerificationBlock sign-in until the address is verified
autoSignInSign the user in immediately after sign-up
account.accountLinkingLink a social account to an existing email/password user
requireAdminInviteForSignUpRequire an invitation for all public sign-ups

For the full set, see the Better Auth options reference.

On this page