Better Enrollment

Recipes

Short patterns for common setups.

buildInviteUrl: ({ token }) => `${process.env.APP_URL}/invite?token=${encodeURIComponent(token)}`,

Captcha on redemption endpoints

captcha({ endpoints: ["/invite/redeem", "/invite/accept", "/invite/activate"] });

Breached password checks for invited users

haveIBeenPwned({
  paths: ["/sign-up/email", "/reset-password", "/invite/redeem", "/invite/accept"]
});

Gating org creation by role

An org-create invite bypasses this gate by design; the invitation is itself the authorization:

organization({ allowUserToCreateOrganization: roleGate(["admin", "org-creator"]) });

Changing roles without losing invite-granted ones

Better Auth stores multiple roles as one comma-separated string, and the admin plugin's setRole replaces the whole field with whatever you send. Invite redemption grants roles by merging into that field, so a bare setRole({ role: "admin" }) from your admin UI silently strips anything an invite granted earlier (such as org-creator). Always compute the union first:

const roles = (user.role || "user")
  .split(",")
  .map((r) => r.trim())
  .filter(Boolean);

if (!roles.includes("admin")) {
  await authClient.admin.setRole({ userId: user.id, role: [...roles, "admin"] });
}

Demoting is the same in reverse: filter the role out and send what remains, falling back to your default role when the list would be empty.

Last updated on

On this page