HeliconiaIO

payload better auth

by HeliconiaIO
Authentication & SSO
v3
^3.84.1
MIT

Overview

Summary generated from the README

Replaces default Payload authentication with Better Auth. A custom auth strategy maps Better Auth sessions to req.user so Payload access control keeps working. The full Better Auth config is passed through, with Payload-styled admin login, forgot and reset views. Supports MongoDB and SQLite, requires Next.js and a Payload email adapter for resets and OTP.

Features

  • Full Better Auth config passthrough for plugins, social providers, sessions and hooks
  • Auth strategy that maps Better Auth sessions to req.user
  • Optional forced email OTP verification for admin users
  • Payload-styled admin login, forgot password and reset views
  • Bundled frontend auth client and Next.js route handlers
  • Can be disabled while keeping email/password users able to log in via hash sync

Use cases

  • Add GitHub or other social sign-in to a Payload app using Better Auth
  • Require email one-time-code verification for admin panel users
  • Migrate from Payload local auth to Better Auth without locking out users

From the author

Replace Payload CMS auth with Better Auth — full config passthrough, admin 2FA, and Payload access control via req.user

Installation

npm i @heliconia/payload-better-auth

1

Stars

0

Forks

0

Open Issues

1 months ago

Last Updated

0

Weekly Downloads

v

npm Version

Dependencies

GitHub stars

Trends appear after 7 days of data. Collecting since Oct 22 — 2 daily snapshots so far.

npm weekly downloads

Trends appear after 7 days of data. Daily snapshots have not started yet.

Each point is npm's rolling 7-day download count.

Health Score

30/100Fair

Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.

Badge for your README

Payload Directory health badge
Markdown
[![Payload Directory](https://payloaddirectory.dev/badge/1266960418-root.svg)](https://payloaddirectory.dev/plugins/1266960418-root)
HTML
<a href="https://payloaddirectory.dev/plugins/1266960418-root"><img src="https://payloaddirectory.dev/badge/1266960418-root.svg" alt="Payload Directory"></a>
Updated Sep 9, 2026Created Jun 12, 2026

README

@heliconia/payload-better-auth

Payload CMS 3 plugin that replaces default Payload auth with Better Auth.

Features

  • Full Better Auth config passthrough (plugins, socialProviders, session, hooks, etc.)
  • Payload custom auth strategy maps Better Auth sessions to req.user
  • Payload Local API database adapter (MongoDB + SQLite in v1)
  • Payload-styled admin login, forgot, and reset views
  • Optional forceAdmin2fa email OTP gate at /admin/verify-identity
  • Seamless plugin enable/disable for email/password users (Payload hash sync)
  • Frontend auth client export for Next.js apps

Install

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

Peer dependencies: payload, better-auth, next, @payloadcms/next.

Environment

PAYLOAD_SECRET=...              # 32+ chars recommended
BETTER_AUTH_SECRET=...          # or reuse PAYLOAD_SECRET
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=...

# Optional
FORCE_ADMIN_2FA=true            # admin OTP gate (also exposed to admin UI)
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...

Payload config

import { payloadBetterAuth } from '@heliconia/payload-better-auth'
import { admin } from 'better-auth/plugins'

export default buildConfig({
  admin: { user: 'users' },
  collections: [Users],
  plugins: [
    payloadBetterAuth({
      usersCollection: 'users',
      forceAdmin2fa: false,
      rolesField: 'roles',
      adminRoles: ['admin'],
      disabled: false,
      betterAuth: ({ payload }) => ({
        emailAndPassword: { enabled: true },
        socialProviders: {
          github: {
            clientId: process.env.GITHUB_CLIENT_ID!,
            clientSecret: process.env.GITHUB_CLIENT_SECRET!,
          },
        },
        plugins: [admin({ defaultRole: 'user', adminRoles: ['admin'] })],
      }),
    }),
  ],
})

betterAuth accepts an object or callback. The plugin injects database, secret, baseURL, and trustedOrigins — do not set those manually.

Disable the plugin

payloadBetterAuth({
  disabled: true,
  betterAuth: () => ({}),
})

Or set PLUGIN_DISABLED=true in the dev app. Payload local auth is restored. Email/password users who signed up via Better Auth can still log in (Payload hash/salt is synced on sign-up, sign-in, and reset). See docs/migration.md.

Next.js auth API route

// app/api/auth/[...all]/route.ts
import { createAuthRouteHandlers } from '@heliconia/payload-better-auth'
import { getPayload } from 'payload'
import config from '@payload-config'

export const { GET, POST, PATCH, PUT, DELETE } = createAuthRouteHandlers(() =>
  getPayload({ config }),
)

Frontend authentication

Use the bundled client in React/Next.js client components:

'use client'

import { authClient, requestPasswordReset } from '@heliconia/payload-better-auth/client'

Email sign-in

const { error } = await authClient.signIn.email({
  email: '[email protected]',
  password: 'secret',
})

if (error) {
  // handle error
}

Email sign-up

await authClient.signUp.email({
  email: '[email protected]',
  password: 'secret',
  name: 'Jane',
})

Social sign-in (GitHub example)

Configure socialProviders.github in betterAuth, then:

await authClient.signIn.social({
  provider: 'github',
  callbackURL: '/dashboard',
})

Ensure BETTER_AUTH_URL matches your app origin and OAuth redirect URLs are registered with the provider.

Password reset

await requestPasswordReset({
  email: '[email protected]',
  redirectTo: `${window.location.origin}/auth/reset-password`,
})

On the reset page, read ?token= from the URL and call:

await authClient.resetPassword({
  token,
  newPassword: 'new-secret',
})

Session

const { data } = await authClient.getSession()
const user = data?.user

Sign out

await authClient.signOut()

Custom client

import { createPayloadAuthClient } from '@heliconia/payload-better-auth/client'

export const authClient = createPayloadAuthClient({
  basePath: '/api/auth',
})

Access control

access: {
  read: ({ req }) => req.user?.roles?.includes('admin') ?? false,
}

req.user is populated by the Better Auth strategy when a valid session cookie is present.

Admin UI

When the plugin is enabled, these admin routes use Better Auth:

RouteBehavior
/admin/loginBetter Auth email sign-in
/admin/forgotSends Better Auth reset email
/admin/reset?token=…Better Auth password reset
/admin/verify-identityEmail OTP when forceAdmin2fa: true

Logout calls Better Auth signOut via an afterLogout hook.

Admin 2FA (forceAdmin2fa)

When forceAdmin2fa: true, admin-role users must verify an email OTP at /admin/verify-identity before accessing the dashboard.

The plugin auto-injects emailOTP if missing. Provide sendVerificationOTP in production for real email delivery.

In Next.js, expose the flag to the admin UI:

// next.config.mjs
env: {
  NEXT_PUBLIC_FORCE_ADMIN_2FA: process.env.FORCE_ADMIN_2FA ?? 'false',
},

The admin UI also reads config.admin.custom.payloadBetterAuth.forceAdmin2fa from the plugin.

Email adapter

Password reset and admin 2FA require a Payload email adapter (payload.sendEmail). Without one, reset emails are skipped (logged as warning).

Dev app

The monorepo includes a dev Next.js app:

pnpm dev
URLPurpose
/authDev hub — links to all auth flows
/auth/loginFrontend email + GitHub login
/auth/signupFrontend sign-up
/auth/accountSession probe
/auth/reset-passwordFrontend reset flow
/admin/loginAdmin login
/admin/forgotAdmin forgot password

Copy dev/.env.example to dev/.env. Seeded admin: [email protected] / test-password.

Tests

pnpm test:int
pnpm test:e2e
FORCE_ADMIN_2FA=true pnpm test:e2e:2fa
pnpm build

Supported databases (v1)

  • MongoDB
  • SQLite

Developed by

Heliconia Solutions Pvt. Ltd

License

MIT

Similar plugins