Skip to Content
Delmare Digital

Recipes

Worked patterns that combine Better Auth hooks with Payload, starting with creating an organization when a user signs up.

Patterns that go beyond configuration. Each one uses standard Better Auth APIs.

Create an organization on sign-up

Keep a lazy reference to the auth instance so database hooks can call auth.api.createOrganization(). Going through the API, rather than the adapter, makes Better Auth's organizationHooks fire.

Create an auth instance singleton

src/lib/auth/instance.ts
let authInstance: AuthInstance | null = null

export function setAuthInstance(auth: AuthInstance) { authInstance = auth }
export function getAuthInstance() {
  if (!authInstance) throw new Error('Auth not initialized')
  return authInstance
}

Store it after creation

src/payload.config.ts
createBetterAuthPlugin({
  createAuth: (payload) => {
    const auth = betterAuth({ ...betterAuthOptions, database: payloadAdapter({ payloadClient: payload }) })
    setAuthInstance(auth)
    return auth
  },
})

Use it in database hooks

src/lib/auth/config.ts
databaseHooks: {
  user: {
    update: {
      after: async (user, ctx) => {
        if (!user.emailVerified) return
        const existing = await ctx?.context?.adapter?.findOne({
          model: 'member', where: [{ field: 'userId', value: user.id }],
        })
        if (existing) return

        const auth = getAuthInstance()
        await auth.api.createOrganization({
          body: { name: `${user.name}'s Workspace`, slug: generateSlug(user.name), userId: user.id },
        })
      },
    },
  },
}

Always use auth.api.createOrganization() instead of raw adapter calls. The adapter bypasses organizationHooks entirely.

Only create social users who pass the role gate

See Social sign-in.

Edit this page on GitHub

On this page