Skip to main content
This guide walks you through setting up the invite plugin and creating your first working invitation system.

Server setup

Configure the plugin

Import and configure the invite plugin in your Better Auth server configuration:
auth.ts
import { betterAuth } from "better-auth";
import { admin } from "better-auth/plugins";
import { invite } from "better-auth-invite-plugin";

export const auth = betterAuth({
  database: {
    // your database configuration
  },
  plugins: [
    // Admin plugin is recommended for role management
    admin({
      roles: {
        user: { name: "user" },
        admin: { name: "admin" }
      },
      defaultRole: "user"
    }),
    invite({
      // Where to redirect users after they accept an invite
      defaultRedirectAfterUpgrade: "/auth/invited",
      
      // Email sending function for private invites
      async sendUserInvitation({ email, role, url, token, newAccount, name }) {
        // Send the invitation email using your email service
        await sendEmail({
          to: email,
          subject: newAccount 
            ? `You're invited to join our platform!` 
            : `Role upgrade invitation`,
          html: `
            <p>Hi ${name || 'there'},</p>
            <p>${newAccount 
              ? 'You have been invited to create an account.' 
              : 'You have been invited to upgrade your role.'}
            </p>
            <p>Role: ${role}</p>
            <p><a href="${url}">Click here to accept</a></p>
            <p>Or use this code: ${token}</p>
          `
        });
      }
    })
  ],
  emailAndPassword: {
    enabled: true
  }
});
Replace sendEmail with your actual email sending function. Popular options include Resend, SendGrid, Nodemailer, or AWS SES.

Configuration options

Here are some commonly used configuration options:
invite({
  // Redirect URLs
  defaultRedirectAfterUpgrade: "/welcome",
  defaultRedirectToSignUp: "/auth/sign-up",
  defaultRedirectToSignIn: "/auth/sign-in",
  
  // Token settings
  defaultTokenType: "token", // "token" | "code" | "custom"
  invitationTokenExpiresIn: 60 * 60 * 24 * 7, // 7 days in seconds
  
  // Usage limits
  defaultMaxUses: 1, // For private invites
  
  // Privacy
  defaultShareInviterName: true,
  
  // Email function
  async sendUserInvitation({ email, role, url, token, newAccount }) {
    // Your email logic here
  },
  
  // Permission checks (optional)
  canCreateInvite: async ({ inviterUser, invitedUser, ctx }) => {
    // Custom permission logic
    return inviterUser.role === "admin";
  }
})

Client setup

Configure the client plugin

Add the inviteClient plugin to your Better Auth client:
auth-client.ts
import { createAuthClient } from "better-auth/client";
import { adminClient } from "better-auth/client/plugins";
import { inviteClient } from "better-auth-invite-plugin";

export const authClient = createAuthClient({
  baseURL: "http://localhost:3000",
  plugins: [
    adminClient(),
    inviteClient()
  ]
});
The client plugin automatically infers the server plugin’s types, giving you full TypeScript support.

Create your first invite

Now you can create invitations from your application code.

Private invite (with email)

Private invites are sent to a specific email address:
import { authClient } from "@/lib/auth-client";

// User must be authenticated to create invites
const { data, error } = await authClient.invite.create({
  role: "admin",
  email: "[email protected]",
  // The email will be sent automatically via sendUserInvitation
});

if (error) {
  console.error("Failed to create invite:", error);
} else {
  console.log("Invite sent successfully!");
  // Response: { status: true, message: "The invitation was sent" }
}
Public invites generate a token or URL that can be shared with anyone:
const { data, error } = await authClient.invite.create({
  role: "user",
  senderResponse: "token" // Returns the token
});

if (data) {
  console.log("Invite token:", data.message);
  // Share this token with your users
  // Example: "kx8f9j2l3m4n5p6q7r8s9t0u"
}
To get a full URL instead:
const { data, error } = await authClient.invite.create({
  role: "user",
  senderResponse: "url" // Returns the full URL
});

if (data) {
  console.log("Invite URL:", data.message);
  // Example: "http://localhost:3000/api/auth/invite/TOKEN?callbackURL=%2Fauth%2Fsign-up"
}

Custom token types

const { data, error } = await authClient.invite.create({
  role: "user",
  tokenType: "code",
  senderResponse: "token"
});

// Returns a 6-character code like "A7B9C2"

Activate an invite

When a user receives an invite, they need to activate it before signing up or signing in. If the user clicks the email link, the invite is automatically activated via the callback URL. The token is stored in a cookie and will be consumed when they complete sign-up or sign-in.

Manual activation

You can also manually activate an invite using the API:
const { data, error } = await authClient.invite.activate({
  token: "kx8f9j2l3m4n5p6q7r8s9t0u",
  callbackURL: "/auth/sign-up" // Optional: where to redirect
});

if (error) {
  console.error("Failed to activate invite:", error);
} else {
  console.log("Invite activated!", data);
  // Cookie is now set with the invite token
  // Redirect user to sign up or sign in
}
After activation, a cookie named {your-app-name}.invite-code is set in the user’s browser. This cookie will be automatically validated and consumed during authentication.

Complete the flow

After activating the invite, the user proceeds to sign up or sign in:
// If it's a new user
const { data, error } = await authClient.signUp.email({
  email: "[email protected]",
  password: "securePassword123",
  name: "Jane Doe"
});

// OR if user already exists
const { data, error } = await authClient.signIn.email({
  email: "[email protected]",
  password: "securePassword123"
});
1

Hook triggers

After successful authentication, the plugin’s hook automatically runs.
2

Validation

The invite token from the cookie is validated (expiration, max uses, etc.).
3

Role assignment

The user’s role is set or upgraded according to the invite.
4

Tracking

A record is created in the inviteUse table tracking who used the invite.
5

Cleanup

The invite cookie is cleared.
6

Redirect

The user is redirected to defaultRedirectAfterUpgrade (e.g., /auth/invited).

Handle the welcome page

Create a welcome page at the redirect URL to show users their new role:
app/auth/invited/page.tsx
"use client";

import { useSession } from "@/lib/auth-client";

export default function InvitedPage() {
  const { data: session } = useSession();
  
  return (
    <div>
      <h1>Welcome to the platform!</h1>
      <p>You've successfully joined with the role: {session?.user.role}</p>
      <a href="/dashboard">Go to Dashboard</a>
    </div>
  );
}

Additional operations

Get invite information

const { data, error } = await authClient.invite.get({
  query: {
    token: "kx8f9j2l3m4n5p6q7r8s9t0u"
  }
});

if (data) {
  console.log("Inviter:", data.inviter.name);
  console.log("Role:", data.invitation.role);
  console.log("New account:", data.invitation.newAccount);
}

Cancel an invite

Only the user who created the invite can cancel it:
const { data, error } = await authClient.invite.cancel({
  token: "kx8f9j2l3m4n5p6q7r8s9t0u"
});

if (data) {
  console.log("Invite cancelled");
}

Reject an invite

For private invites, the invitee can reject the invitation:
const { data, error } = await authClient.invite.reject({
  token: "kx8f9j2l3m4n5p6q7r8s9t0u"
});

if (data) {
  console.log("Invite rejected");
}

Testing your setup

Here’s a simple test to verify everything works:
1

Create a test invite

const { data } = await authClient.invite.create({
  role: "user",
  senderResponse: "token"
});
const token = data?.message;
2

Activate it

await authClient.invite.activate({ token });
3

Sign up

await authClient.signUp.email({
  email: "[email protected]",
  password: "test123",
  name: "Test User"
});
4

Check the role

const session = await authClient.getSession();
console.log(session?.user.role); // Should be "user"

Next steps

Configuration

Explore all available configuration options

API Reference

Complete API documentation for all endpoints

Hooks

Customize behavior with lifecycle hooks

Examples

See real-world implementation examples

Build docs developers (and LLMs) love