<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sandy</title>
    <description>The latest articles on DEV Community by Sandy (@sandy00).</description>
    <link>https://dev.to/sandy00</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3762143%2F1fd43e93-8af4-4ad5-8499-f66940b96d00.png</url>
      <title>DEV Community: Sandy</title>
      <link>https://dev.to/sandy00</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sandy00"/>
    <language>en</language>
    <item>
      <title>Automate Role Assignments in Better Auth with Role-Based Invitations</title>
      <dc:creator>Sandy</dc:creator>
      <pubDate>Mon, 09 Feb 2026 15:14:04 +0000</pubDate>
      <link>https://dev.to/sandy00/stop-manually-assigning-roles-in-better-auth-use-role-based-invitations-instead-2bd7</link>
      <guid>https://dev.to/sandy00/stop-manually-assigning-roles-in-better-auth-use-role-based-invitations-instead-2bd7</guid>
      <description>&lt;p&gt;Better Auth makes authentication feel clean and modern. Sessions, adapters, client/server separation — it’s a great developer experience.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How do I give roles to users in a simple, safe, and scalable way?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sure, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manually edit the database&lt;/li&gt;
&lt;li&gt;write a temporary admin script&lt;/li&gt;
&lt;li&gt;hardcode a list of privileged emails&lt;/li&gt;
&lt;li&gt;build a quick internal endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…but those approaches don’t scale, and they usually turn into security and maintenance debt.&lt;/p&gt;

&lt;p&gt;This post introduces a practical solution: &lt;strong&gt;role-based invitations&lt;/strong&gt; for Better Auth, implemented as a small plugin you can drop into your project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why roles become painful in real apps
&lt;/h2&gt;

&lt;p&gt;Roles are not hard. You can store a role on the user record, or map roles in a separate table.&lt;/p&gt;

&lt;p&gt;The painful part is &lt;strong&gt;role onboarding&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who is allowed to grant roles?&lt;/li&gt;
&lt;li&gt;what role can they grant?&lt;/li&gt;
&lt;li&gt;how do you track who granted it?&lt;/li&gt;
&lt;li&gt;how do you avoid mistakes?&lt;/li&gt;
&lt;li&gt;how do you avoid creating a permanent security hole?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you only have two roles, this becomes a real problem surprisingly fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  The common “solutions” (and why they’re not great)
&lt;/h2&gt;

&lt;p&gt;Most teams start with something like:&lt;/p&gt;

&lt;h3&gt;
  
  
  “Just update the user in the DB”
&lt;/h3&gt;

&lt;p&gt;This works… until you have to do it multiple times per week.&lt;/p&gt;

&lt;p&gt;It also doesn’t scale if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you have multiple environments&lt;/li&gt;
&lt;li&gt;you have multiple admins&lt;/li&gt;
&lt;li&gt;you want auditability&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  “Let admins set roles in an internal UI”
&lt;/h3&gt;

&lt;p&gt;This can be fine — but it’s still extra work.&lt;/p&gt;

&lt;p&gt;You’re building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI&lt;/li&gt;
&lt;li&gt;authorization&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;audit logs&lt;/li&gt;
&lt;li&gt;edge case handling&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  “Hardcode special users”
&lt;/h3&gt;

&lt;p&gt;This is a trap.&lt;/p&gt;

&lt;p&gt;You end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hardcoded emails&lt;/li&gt;
&lt;li&gt;role logic in random places&lt;/li&gt;
&lt;li&gt;unclear ownership of permissions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A better approach: Role assignment via invitations
&lt;/h2&gt;

&lt;p&gt;Instead of directly assigning roles, you can use a cleaner model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If someone is allowed to give a role, they should be able to create an invitation that grants that role.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That invitation becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit&lt;/li&gt;
&lt;li&gt;trackable&lt;/li&gt;
&lt;li&gt;revocable&lt;/li&gt;
&lt;li&gt;safe to share&lt;/li&gt;
&lt;li&gt;easy to test locally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also naturally supports real product flows like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invite-only signups&lt;/li&gt;
&lt;li&gt;onboarding staff or moderators&lt;/li&gt;
&lt;li&gt;team invites for SaaS&lt;/li&gt;
&lt;li&gt;beta programs&lt;/li&gt;
&lt;li&gt;role upgrades&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introducing Better Invite
&lt;/h2&gt;

&lt;p&gt;Better Invite is a Better Auth plugin (not officially affiliated with Better Auth) that adds &lt;strong&gt;role-based invitations&lt;/strong&gt; to your auth setup.&lt;/p&gt;

&lt;p&gt;It’s intentionally designed to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;lightweight&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;highly customizable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;not opinionated&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;safe by default&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;and easy to integrate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plugin handles the hard parts of the invitation flow, while you stay in control of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who can create invites&lt;/li&gt;
&lt;li&gt;what roles can be granted&lt;/li&gt;
&lt;li&gt;how invites are delivered&lt;/li&gt;
&lt;li&gt;how redirects work&lt;/li&gt;
&lt;li&gt;how tokens should look&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What you get (features)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create invitations for users&lt;/li&gt;
&lt;li&gt;Accept and activate invitations&lt;/li&gt;
&lt;li&gt;Assign or upgrade roles automatically&lt;/li&gt;
&lt;li&gt;Track who created the invite&lt;/li&gt;
&lt;li&gt;Track who used the invite&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Safety features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Expiration support&lt;/li&gt;
&lt;li&gt;Max uses support&lt;/li&gt;
&lt;li&gt;Optional private invitations (email-bound)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Customization features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Custom token types (default token, code, or fully custom tokens)&lt;/li&gt;
&lt;li&gt;Custom redirect behavior&lt;/li&gt;
&lt;li&gt;Fully customizable permission rules for invite creation&lt;/li&gt;
&lt;li&gt;Flexible role assignment logic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The real question: Who is allowed to grant roles?
&lt;/h2&gt;

&lt;p&gt;This is where most invite systems fail.&lt;/p&gt;

&lt;p&gt;The important question isn’t just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Can this user create an invite?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Should this user be allowed to grant this specific role?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Different apps have different rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only admins can grant roles&lt;/li&gt;
&lt;li&gt;moderators can invite regular users&lt;/li&gt;
&lt;li&gt;staff can invite staff, but not admins&lt;/li&gt;
&lt;li&gt;users can only invite roles lower than their own&lt;/li&gt;
&lt;li&gt;verified users only&lt;/li&gt;
&lt;li&gt;limit invites per user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that’s exactly why this plugin doesn’t ship with a hardcoded permission model.&lt;/p&gt;

&lt;p&gt;Instead, it gives you the building blocks — and you decide what “safe” means for your app.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;canCreateInvite&lt;/code&gt; (optional, but strongly recommended)
&lt;/h2&gt;

&lt;p&gt;By default, &lt;strong&gt;any authenticated user can create an invite&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s intentional: the plugin stays flexible and doesn’t assume your role hierarchy.&lt;/p&gt;

&lt;p&gt;But in most real apps, you’ll want to restrict invite creation.&lt;/p&gt;

&lt;p&gt;That’s what &lt;code&gt;canCreateInvite&lt;/code&gt; is for.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;canCreateInvite&lt;/code&gt; is an &lt;strong&gt;optional plugin option&lt;/strong&gt; that runs before an invite is created, and lets you enforce your rules safely on the server.&lt;/p&gt;

&lt;p&gt;Example policies you can implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only admins can create invites&lt;/li&gt;
&lt;li&gt;Only users above a role threshold can create invites&lt;/li&gt;
&lt;li&gt;Users can only invite people into roles lower than theirs&lt;/li&gt;
&lt;li&gt;Block invite creation for unverified users&lt;/li&gt;
&lt;li&gt;Limit how many invites a user can generate per day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: the plugin handles the invitation flow, while your app stays in full control of permissions.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;canCreateInvite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;invitedUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inviterUser&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;inviterUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RoleHierarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;// If the inviter isn't trying to invite a user with a higher role than his, he can create the invite&lt;/span&gt;
    &lt;span class="nx"&gt;RoleHierarchy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;inviterUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;RoleType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;RoleHierarchy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;invitedUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;RoleType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How the invitation flow works (high-level)
&lt;/h2&gt;

&lt;p&gt;Here’s the full flow the plugin is designed around:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A signed-in user creates an invite.&lt;/li&gt;
&lt;li&gt;(Optional) &lt;code&gt;canCreateInvite&lt;/code&gt; runs and approves/denies.&lt;/li&gt;
&lt;li&gt;The invite is generated with:

&lt;ul&gt;
&lt;li&gt;a token&lt;/li&gt;
&lt;li&gt;optional expiration&lt;/li&gt;
&lt;li&gt;optional max uses&lt;/li&gt;
&lt;li&gt;optional email (for private invites)&lt;/li&gt;
&lt;li&gt;a target role&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The invite is shared:

&lt;ul&gt;
&lt;li&gt;as a link&lt;/li&gt;
&lt;li&gt;as a code&lt;/li&gt;
&lt;li&gt;or via email&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The invited user opens the link.&lt;/li&gt;
&lt;li&gt;If they’re not signed in, they sign in.&lt;/li&gt;
&lt;li&gt;They activate the invite.&lt;/li&gt;
&lt;li&gt;Their role is assigned/upgraded automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This solves the most annoying part of role onboarding:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No manual database work.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Quickstart
&lt;/h2&gt;

&lt;p&gt;How to use the plugin&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;better-invite
&lt;span class="c"&gt;# or&lt;/span&gt;
pnpm add better-invite
&lt;span class="c"&gt;# or&lt;/span&gt;
yarn add better-invite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) Add it to your Better Auth server config
&lt;/h3&gt;

&lt;p&gt;In your Better Auth config (server side), register the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invitePlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;better-invite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;adminPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;better-auth/plugins&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;betterAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;adminPlugin &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;defaultRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nf"&gt;invitePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;defaultRedirectAfterUpgrade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/invited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;sendUserInvitation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendInvitationEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;RoleType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&gt;// canCreateInvite: async (...) =&amp;gt; true/false&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3) Add it to the Better Auth client config
&lt;/h3&gt;

&lt;p&gt;On the client side, enable the plugin integration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;inviteClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;better-invite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;adminClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;better-auth/client/plugins&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;//... other options&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;adminClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;inviteClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Creating invitations
&lt;/h2&gt;

&lt;p&gt;Once the plugin is installed, your app can create invitations from a secure server route or server action.&lt;/p&gt;

&lt;p&gt;A typical invite might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;role to grant&lt;/li&gt;
&lt;li&gt;expiration (optional)&lt;/li&gt;
&lt;li&gt;max uses (optional)&lt;/li&gt;
&lt;li&gt;email (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Here you put the options&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// This makes the invite private&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Public vs Private invitations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Public invites (shareable)
&lt;/h3&gt;

&lt;p&gt;A public invite is a link (or code) you can share.&lt;/p&gt;

&lt;p&gt;Use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invite teammates&lt;/li&gt;
&lt;li&gt;invite community members&lt;/li&gt;
&lt;li&gt;invite users into a beta&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Private invites (email-bound)
&lt;/h3&gt;

&lt;p&gt;A private invite includes an email.&lt;/p&gt;

&lt;p&gt;Use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;onboarding staff&lt;/li&gt;
&lt;li&gt;inviting a specific person&lt;/li&gt;
&lt;li&gt;ensuring only one person can use the invite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you can use Resend, Postmark, SendGrid, SMTP, or anything else&lt;/li&gt;
&lt;li&gt;your app handles the actual sending&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Token types: links, codes, or fully custom tokens
&lt;/h2&gt;

&lt;p&gt;Not every app wants the same invite format.&lt;/p&gt;

&lt;p&gt;Some apps want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;long secure tokens&lt;/li&gt;
&lt;li&gt;short human-friendly codes&lt;/li&gt;
&lt;li&gt;custom tokens that integrate with existing flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the plugin supports multiple token types, including custom tokens if you need advanced behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;I originally needed a clean way to grant roles without:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manually editing the database&lt;/li&gt;
&lt;li&gt;building a full admin UI&lt;/li&gt;
&lt;li&gt;or writing one-off scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also wanted something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fits naturally into Better Auth’s plugin ecosystem&lt;/li&gt;
&lt;li&gt;doesn’t assume a specific permission model&lt;/li&gt;
&lt;li&gt;stays flexible for real-world apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After implementing the actual code, I realized it could be useful to other Better Auth users — so I extracted it into a plugin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Credits / Inspiration
&lt;/h2&gt;

&lt;p&gt;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  &lt;a href="https://github.com/bard/better-auth-invite" rel="noopener noreferrer"&gt;bard/better-auth-invite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That repository helped me think through the full lifecycle more clearly, especially the “end-to-end” parts of the flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating the invite&lt;/li&gt;
&lt;li&gt;delivering it via email&lt;/li&gt;
&lt;li&gt;activating it after sign-in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So huge thanks for the idea and the inspiration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contributing and feedback
&lt;/h2&gt;

&lt;p&gt;This plugin is actively evolving, and feedback is welcome.&lt;/p&gt;

&lt;p&gt;If you try it and you want improvements, feel free to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open an issue&lt;/li&gt;
&lt;li&gt;suggest features&lt;/li&gt;
&lt;li&gt;share edge cases&lt;/li&gt;
&lt;li&gt;submit a PR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even small contributions (readme, examples, bug reports) help a lot.&lt;/p&gt;

&lt;p&gt;Github: &lt;a href="https://github.com/better-invite/better-invite" rel="noopener noreferrer"&gt;better-invite&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://npmjs.com/package/better-invite" rel="noopener noreferrer"&gt;better-invite&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final CTA
&lt;/h2&gt;

&lt;p&gt;If you’re using Better Auth and you’re tired of manually assigning roles, give Better Invite a try.&lt;/p&gt;

&lt;p&gt;And please consider starring the GitHub repo — it helps a lot with visibility, and adoption.&lt;/p&gt;

</description>
      <category>betterauth</category>
      <category>plugins</category>
      <category>backend</category>
      <category>invite</category>
    </item>
  </channel>
</rss>
