Next session: Revert to native Payload auth, add Better Auth alongside for members only

Context

We spent a session on feat/payload-auth trying to integrate payload-auth (Better Auth plugin for Payload). Results:

What worked:

  • Removed @payloadcms/plugin-multi-tenant — all users visible, org CRUD works via REST API
  • Squashed migrations into a single baseline (requires DB nuke on staging)
  • Frontend auth flows (login, signup, verify-email, forgot/reset password) all wired up
  • Better Auth session-based auth works for API routes

What’s broken (payload-auth showstopper):

  • Admin panel can’t create, edit, or even type in ANY collection
  • form-state server action re-renders the entire RSC tree on every keystroke, resetting inputs
  • Confirmed on fresh DB with fresh user — not a data issue
  • payload-auth 1.9.4 (latest) was built against Payload 3.67.0, we’re on 3.80.0
  • The demo app in the payload-auth repo also targets 3.67.0
  • Adding admin() Better Auth plugin made it worse

Root cause: payload-auth takes over the users collection and injects custom admin panel components (login, logout, form overrides). Something in how it handles server actions is incompatible with Payload 3.80.0’s admin panel rendering.

New plan: Dual auth architecture

Go back to feat/email-verification branch (native Payload auth working, Members collection separate from Users). Add Better Auth as a standalone service for member-facing auth only.

Architecture

Admin panel (Payload native auth)
  └─ Users collection: admin, staff accounts
  └─ Standard Payload JWT — just works

Member portal (Better Auth)
  └─ Members collection: auth: true (Payload native for admin management)
  └─ Better Auth handles: login, signup, Discord OAuth, email verification
  └─ Better Auth sessions stored in its own tables (sessions, accounts, verifications)
  └─ Members collection keeps auth: true so admin panel can still manage members

Steps

  1. Checkout feat/email-verification branch
  2. Remove payload-auth package entirely
  3. Install better-auth directly (already a dependency)
  4. Set up Better Auth standalone:
    • src/lib/auth.ts — Better Auth instance with email/password + Discord OAuth
    • src/app/api/auth/[...all]/route.ts — Better Auth API handler
    • src/lib/auth/client.ts — client-side auth hooks
  5. Better Auth uses Members collection as its user store (via custom adapter or direct DB)
  6. Keep Payload’s native auth for Users collection (admin/staff)
  7. Wire frontend auth pages to Better Auth (login, signup, verify-email, forgot-password)
  8. API routes use Better Auth sessions for member context, Payload auth for admin context
  9. Restore multiTenantPlugin removal from this session (explicit org fields, baseline migration)

What to keep from feat/payload-auth

  • Frontend pages: login, signup, verify-email, forgot-password, reset-password, dashboard
  • UI components: login-form, signup-form, verify-email-handler, etc.
  • The multiTenantPlugin removal commit (explicit org fields on 9 collections)
  • Baseline migration approach (squash all migrations)

What to throw away

  • payload-auth package and all its config
  • betterAuthPlugin() in payload.config.ts
  • betterAuthPluginOptions in lib/auth/options.ts
  • The merged Users collection (revert to separate Users + Members)
  • Any payload-auth-specific collections (admin-invitations)

Key decision

Better Auth needs a user store. Options:

  1. Point Better Auth at the Members collection — use a Payload adapter or drizzle adapter mapping to the members table
  2. Let Better Auth manage its own auth_users table — map to Members via a hook on signup
  3. Use Better Auth’s Payload adapter (from payload-auth source) but without the plugin — cherry-pick just the adapter code

Option 1 is cleanest if it works. Option 2 has more moving parts but zero coupling.

Prompt to paste

We're going back to feat/email-verification branch to take a different approach to auth.

Check memory at project_payload_auth_migration.md for context on what happened.

Short version: payload-auth plugin is incompatible with Payload 3.80.0's admin panel — 
forms can't even accept input (server action re-renders reset all fields on keystroke).

New plan: Keep Payload's native auth for admin/staff (Users collection). Add Better Auth 
as a standalone service for member-facing auth (Members collection) — login, signup, 
Discord OAuth, email verification. No payload-auth plugin, no collection takeover.

Start by:
1. Cherry-pick the multiTenantPlugin removal from feat/payload-auth (commit ed2c87a)
2. Remove payload-auth package
3. Set up Better Auth standalone with Members as the user store
4. Wire the existing frontend auth pages to Better Auth instead of payload-auth