Skip to Content
Delmare Digital

Plugin compatibility

Which Better Auth plugins work with the Payload adapter, which collections they generate, and how to add joins and cascade deletes.

The adapter is built on Better Auth's createAdapterFactory, which is schema-aware, and betterAuthCollections() generates a Payload collection for every model in the installed Better Auth schema. Better Auth plugins that add models or fields work without adapter changes.

PluginPackageNotes
OAuth / social providersbetter-authUses the accounts collection.
Generic OAuth / OIDCbetter-authProviders appear as social providers on the admin login.
Magic Linkbetter-authUses verifications. Admin login UI supported.
Email OTPbetter-authUses verifications. Admin login UI supported.
Two-Factorbetter-authGenerates twoFactors. Admin login and management UI supported.
Organizationsbetter-authGenerates organizations, members, invitations.
JWTbetter-authGenerates jwks.
API Keys@better-auth/api-keyGenerates apikeys. Admin management UI supported.
Passkey@better-auth/passkeyGenerates passkeys. Admin login and management UI supported.
OAuth Provider@better-auth/oauth-providerGenerates the OAuth client and token collections.

Slugs above assume the default usePlural: true.

Do not use nextCookies()

Better Auth's nextCookies() plugin is incompatible with Payload. It causes repeated form-state submissions and input resets in the admin panel. It exists for Server Actions; this plugin already passes cookies through its endpoint proxy. Remove it from your Better Auth plugins. The plugin warns at startup when it detects it.

Join fields

Payload uses join fields for queryable parent-to-child relationships. When a plugin creates a model with a foreign key to the user, add a join field to your users collection:

src/collections/Users/index.ts
// API keys
{ name: 'apiKeys', type: 'join', collection: 'apikeys', on: 'user' }

// Two-factor
{ name: 'twoFactor', type: 'join', collection: 'twoFactors', on: 'user' }

// Organization memberships
{ name: 'memberships', type: 'join', collection: 'members', on: 'user' }

Cascade delete

To clean up a deleted user's records, use Better Auth's deleteUser.afterDelete:

src/lib/auth/config.ts
user: {
  deleteUser: {
    enabled: true,
    afterDelete: async (user) => {
      const collections = ['sessions', 'accounts', 'apikeys', 'passkeys', 'twoFactors']
      for (const col of collections) {
        try {
          await payload.delete({ collection: col, where: { user: { equals: user.id } } })
        } catch {} // Collection may not exist
      }
    },
  },
}

payload here is your Payload instance, in scope where you call betterAuth() inside createAuth: (payload) => ….

Edit this page on GitHub

On this page