Better Auth makes authentication feel clean and modern. Sessions, adapters, client/server separation — it’s a great developer experience.
But when your app gets bigger and you have more than one type of user you will run into a problem. This is a problem that every real product will have to deal with at some point.
How do I give roles to users in a simple, safe, and scalable way?
Sure, you can:
- manually edit the database
- write a temporary admin script
- hardcode a list of privileged emails
- build a quick internal endpoint
…but those approaches don’t scale, and they usually turn into security and maintenance debt.
This post introduces a practical solution: role-based invitations for Better Auth, implemented as a small plugin you can drop into your project.
Why roles become painful in real apps
Roles are not hard. You can store a role on the user record, or map roles in a separate table.
The painful part is role onboarding:
- who is allowed to grant roles?
- what role can they grant?
- how do you track who granted it?
- how do you avoid mistakes?
- how do you avoid creating a permanent security hole?
Even if you only have two roles, this becomes a real problem surprisingly fast.
The common “solutions” (and why they’re not great)
Most teams start with something like:
“Just update the user in the DB”
This works… until you have to do it multiple times per week.
It also doesn’t scale if:
- you have multiple environments
- you have multiple admins
- you want auditability
“Let admins set roles in an internal UI”
This can be fine — but it’s still extra work.
You’re building:
- UI
- authorization
- validation
- audit logs
- edge case handling
“Hardcode special users”
This is a trap.
You end up with:
- hardcoded emails
- role logic in random places
- unclear ownership of permissions
A better approach: Role assignment via invitations
Instead of directly assigning roles, you can use a cleaner model:
If someone is allowed to give a role, they should be able to create an invitation that grants that role.
That invitation becomes:
- explicit
- trackable
- revocable
- safe to share
- easy to test locally
It also naturally supports real product flows like:
- invite-only signups
- onboarding staff or moderators
- team invites for SaaS
- beta programs
- role upgrades
Introducing better-auth-invite-plugin
better-auth-invite-plugin is a Better Auth plugin that adds role-based invitations to your auth setup.
It’s intentionally designed to be:
- lightweight
- highly customizable
- not opinionated
- safe by default
- and easy to integrate
The plugin handles the hard parts of the invitation flow, while you stay in control of:
- who can create invites
- what roles can be granted
- how invites are delivered
- how redirects work
- how tokens should look
What you get (features)
Core features
- Create invitations for users
- Accept and activate invitations
- Assign or upgrade roles automatically
- Track who created the invite
- Track who used the invite
Safety features
- Expiration support
- Max uses support
- Optional private invitations (email-bound)
Customization features
- Custom token types (default token, code, or fully custom tokens)
- Custom redirect behavior
- Fully customizable permission rules for invite creation
- Flexible role assignment logic
The real question: Who is allowed to grant roles?
This is where most invite systems fail.
The important question isn’t just:
“Can this user create an invite?”
It’s:
Should this user be allowed to grant this specific role?
Different apps have different rules:
- only admins can grant roles
- moderators can invite regular users
- staff can invite staff, but not admins
- users can only invite roles lower than their own
- verified users only
- limit invites per user
And that’s exactly why this plugin doesn’t ship with a hardcoded permission model.
Instead, it gives you the building blocks — and you decide what “safe” means for your app.
canCreateInvite (optional, but strongly recommended)
By default, any authenticated user can create an invite.
That’s intentional: the plugin stays flexible and doesn’t assume your role hierarchy.
But in most real apps, you’ll want to restrict invite creation.
That’s what canCreateInvite is for.
canCreateInvite is an optional plugin option that runs before an invite is created, and lets you enforce your rules safely on the server.
Example policies you can implement:
- Only admins can create invites
- Only users above a role threshold can create invites
- Users can only invite people into roles lower than theirs
- Block invite creation for unverified users
- Limit how many invites a user can generate per day
In other words: the plugin handles the invitation flow, while your app stays in full control of permissions.
Here's an example:
canCreateInvite: ({ invitedUser, inviterUser }) => {
if (!inviterUser.role) return false;
const RoleHierarchy = {
user: 1,
admin: 2,
owner: 3,
} as const;
return ( // If the inviter isn't trying to invite a user with a higher role than his, he can create the invite
RoleHierarchy[inviterUser.role as RoleType] >=
RoleHierarchy[invitedUser.role as RoleType]
);
},
How the invitation flow works (high-level)
Here’s the full flow the plugin is designed around:
- A signed-in user creates an invite.
- (Optional)
canCreateInviteruns and approves/denies. - The invite is generated with:
- a token
- optional expiration
- optional max uses
- optional email (for private invites)
- a target role
- The invite is shared:
- as a link
- as a code
- or via email
- The invited user opens the link.
- If they’re not signed in, they sign in.
- They activate the invite.
- Their role is assigned/upgraded automatically.
This solves the most annoying part of role onboarding:
No manual database work.
Quickstart
How to use the plugin
1) Install
npm install better-auth-invite-plugin
# or
pnpm add better-auth-invite-plugin
# or
yarn add better-auth-invite-plugin
2) Add it to your Better Auth server config
In your Better Auth config (server side), register the plugin:
import { invitePlugin } from "better-auth-invite-plugin";
import { admin as adminPlugin } from "better-auth/plugins"
import { ac, user, admin } from "..."
export const auth = betterAuth({
plugins: [
adminPlugin ({
ac,
roles: { user, admin },
defaultRole: "user",
}),
invitePlugin({
defaultMaxUses: 1,
defaultRedirectAfterUpgrade: "/auth/invited",
async sendUserInvitation({ email, role, url }) {
void sendInvitationEmail(role as RoleType, email, url);
},
// canCreateInvite: async (...) => true/false
}),
],
});
3) Add it to the Better Auth client config
On the client side, enable the plugin integration.
import { inviteClient } from "better-auth-invite-plugin";
import { adminClient } from "better-auth/client/plugins"
const client = createClient({
//... other options
plugins: [
adminClient(),
inviteClient()
],
});
Creating invitations
Once the plugin is installed, your app can create invitations from a secure server route or server action.
A typical invite might include:
- role to grant
- expiration (optional)
- max uses (optional)
- email (optional)
Here's an example:
const { data, error } = await client.invite.create({
// Here you put the options
role: "admin",
email: "test@test.com" // This makes the invite private
});
Public vs Private invitations
Public invites (shareable)
A public invite is a link (or code) you can share.
Use cases:
- invite teammates
- invite community members
- invite users into a beta
Private invites (email-bound)
A private invite includes an email.
Use cases:
- onboarding staff
- inviting a specific person
- ensuring only one person can use the invite
Important note:
- you can use Resend, Postmark, SendGrid, SMTP, or anything else
- your app handles the actual sending
Token types: links, codes, or fully custom tokens
Not every app wants the same invite format.
Some apps want:
- long secure tokens
- short human-friendly codes
- custom tokens that integrate with existing flows
So the plugin supports multiple token types, including custom tokens if you need advanced behavior.
Why I built this
I originally needed a clean way to grant roles without:
- manually editing the database
- building a full admin UI
- or writing one-off scripts
I also wanted something that:
- fits naturally into Better Auth’s plugin ecosystem
- doesn’t assume a specific permission model
- stays flexible for real-world apps
After implementing the actual code, I realized it could be useful to other Better Auth users — so I extracted it into a plugin.
Credits / Inspiration
Even though I already had the idea of using an invite-based system for role onboarding, a big inspiration for the full flow came from the project bard/better-auth-invite.
That repository helped me think through the full lifecycle more clearly, especially the “end-to-end” parts of the flow:
- creating the invite
- delivering it via email
- activating it after sign-in
So huge thanks for the idea and the inspiration.
Contributing and feedback
This plugin is actively evolving, and feedback is welcome.
If you try it and you want improvements, feel free to:
- open an issue
- suggest features
- share edge cases
- submit a PR
Even small contributions (readme, examples, bug reports) help a lot.
Github: better-auth-invite-plugin
npm: better-auth-invite-plugin
Final CTA
If you’re using Better Auth and you’re tired of manually assigning roles, give better-auth-invite-plugin a try.
And please consider starring the GitHub repo — it helps a lot with visibility, and adoption.
Top comments (0)