# Get Started: Basic Usage
URL: /docs/basic-usage
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/(get-started)/basic-usage.mdx
Learn how to use the plugin.
---
Creating an invite [#creating-an-invite]
To invite a user to our application, we need to create an invite. We can create a public or private invite:
* If the invite has an **email** the user will get an invite email and the invite will be **private**, meaning only that user can use that invite.
* But if no email is present when creating the invite, the invite will be **public**, and the user that created the invite will receive the invite url or token to share.
Example:
```ts
const res = authClient.invite.create({
role: "user",
email: "test@test.com" // If email is present, the invite is private
})
```
If the invite is private, the plugin will send an email to the user with the invite link (see [Activate Invite Callback](/docs/core/api#activate-invite-callback)).
The response from `invite.create()` depends on whether the invite is **public** or **private**:
Public invite [#public-invite]
No email is sent, and the API returns the invite **token or URL** (depending on your `senderResponse` config):
```ts
{
status: true,
message: "token", // The invite token or URL
}
```
Private invite [#private-invite]
The plugin will send an invite email, and the response will be:
```ts
{
status: true,
message: "The invitation was sent",
}
```
Activating an invite [#activating-an-invite]
Activating an invite handles two cases depending on the user's session:
* **If the user is not signed in**, the invite is stored in a temporary cookie and the API returns an action telling you the user must sign in or sign up first.
* **If the user is already signed in**, the user's role is updated immediately.
Example:
```ts
const res = await authClient.invite.activate({
callbackURL: "/auth/sign-in-or-sign-up", // The user will be redirected here if they need to sign in or sign up
token: "token123",
});
```
If the user is not signed in [#if-the-user-is-not-signed-in]
The API returns:
```ts
{
status: true,
message: "Invite activated successfully",
action: "SIGN_IN_UP_REQUIRED",
redirectTo: "/auth/sign-in-or-sign-up",
}
```
At this point, redirect the user to `redirectTo` and perform sign in or sign up. After that, the plugin will automatically activate the invite using a hook, reading the invite data from the cookie. **No further response is returned from the hook**—it just completes the activation once the user has a session.
If the user is already signed in [#if-the-user-is-already-signed-in]
The API returns:
```ts
{
status: true,
message: "Invite activated successfully",
}
```
# Get Started: Installation
URL: /docs/installation
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/(get-started)/installation.mdx
Learn how to configure the invite plugin in your project.
---
Install the package [#install-the-package]
Start by adding the invite plugin to your project:
npm
pnpm
yarn
bun
```bash
npm install better-invite
```
```bash
pnpm add better-invite
```
```bash
yarn add better-invite
```
```bash
bun add better-invite
```
Add the plugin to your auth config [#add-the-plugin-to-your-auth-config]
Add the invite plugin to your auth configuration, make sure you also have the plugin admin.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { admin } from "better-auth/plugins";
import { invite } from "better-invite"; // [!code highlight]
export const auth = betterAuth({
// ... other config options
plugins: [
admin({
ac,
roles: { user, admin },
defaultRole: "user",
}),
invite({ // [!code highlight]
defaultRedirectAfterUpgrade: "/auth/invited?token={token}", // Show the user their new role or account
async sendUserInvitation({ email, role, url, token, newAccount }) {
if (newAccount)
void sendInvitationEmail(role as RoleType, email, url);
void sendRoleUpgradeEmail(role as RoleType, email, url);
},
}),
],
});
```
Migrate the database [#migrate-the-database]
Run the migration or generate the schema to add the necessary fields and tables to the database.
npm
pnpm
yarn
bun
```bash
npx @better-auth/cli migrate
```
```bash
pnpm dlx @better-auth/cli migrate
```
```bash
yarn dlx @better-auth/cli migrate
```
```bash
bun x @better-auth/cli migrate
```
npm
pnpm
yarn
bun
```bash
npx @better-auth/cli generate
```
```bash
pnpm dlx @better-auth/cli generate
```
```bash
yarn dlx @better-auth/cli generate
```
```bash
bun x @better-auth/cli generate
```
See the [Schema](/docs/core/schema) section to add the fields manually.
Add the client plugin [#add-the-client-plugin]
Add the client plugin to your better auth client.
```ts
import { createAuthClient } from "better-auth/client";
import { adminClient } from "better-auth/client/plugins";
import { inviteClient } from "better-invite"; // [!code highlight]
const authClient = createAuthClient({
//... other options
plugins: [
adminClient(),
inviteClient(), // [!code highlight]
],
});
```
# Get Started: Introduction
URL: /docs/introduction
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/(get-started)/introduction.mdx
Introduction to the invite plugin
---
What is Better Invite? [#what-is-better-invite]
Better Invite is a [Better Auth](https://www.better-auth.com) plugin that adds a complete invitation system to your app, allowing you to create and manage invites for both new users and existing accounts.
The plugin is maintained by and is **not officially affiliated with Better Auth**.
How invites work [#how-invites-work]
With better-invite, you can easily invite people to join your app or grant them specific roles. The system is flexible and designed to cover the most common invitation flows without adding unnecessary complexity.
There are two main types of invitations:
* **Private Invitations**: These are tied to a specific email address. Only the person who receives the invite at that email can accept it.
* **Public Invitations**: These can be used by anyone who has access to the invite link or code. They're perfect for broader campaigns or community-based access. For example, you might create a public invite that grants a `discord-member` role to users who joined through your Discord server.
Features [#features]
... and much, much more
License [#license]
The plugin is licensed under the [MIT License](https://github.com/better-invite/better-invite/blob/main/LICENSE)
Acknowledgements [#acknowledgements]
Inspired in the [invite system](https://github.com/bard/better-auth-invite) from Max.
# Core: API
URL: /docs/core/api
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/core/api.mdx
Invitation API endpoints and routes.
---
Create Invite [#create-invite]
```ts
type createInvite = {
/**
* The role to give the invited user.
*/
role: string
/**
* The email address of the user to send a invitation email to.
*/
email?: string // If email is present, the invite is private
/**
* Type of token to use, 24 character token,
* 6 digit code or custom options.generateToken.
* @default options.defaultTokenType
*/
tokenType?: "token" | "code" | "custom"
/**
* The URL to redirect the user to create their account.
* If the token isn't valid or expired, it'll be redirected with a query parameter
* `?error=INVALID_TOKEN`.
* If the token is valid, it'll be redirected with a query parameter
* `?token=VALID_TOKEN`.
*
* @default options.defaultRedirectTo
*/
redirectToSignUp?: string
/**
* The URL to redirect the user to upgrade their role.
* If the token isn't valid or expired, it'll be redirected with a query parameter
* `?error=INVALID_TOKEN`.
* If the token is valid, it'll be redirected with a query parameter
* `?token=VALID_TOKEN`.
*
* @default options.defaultRedirectToSignIn
*/
redirectToSignIn?: string
/**
* The number of times an invitation can be used.
*
* @default options.defaultMaxUses
*/
maxUses?: number
/**
* Number of seconds the invitation token is valid for.
*
* @default options.invitationTokenExpiresIn
*/
expiresIn?: number
/**
* The URL to redirect the user to after upgrading their role (if the user is already logged in).
* {token} will be replaced with the user's actual token.
*
* @default options.defaultRedirectAfterUpgrade
*/
redirectToAfterUpgrade?: string
/**
* Whether the inviter's name should be shared with the invitee.
*
* When enabled, the person receiving the invitation will see
* the name of the user who created the invitation.
*
* @default options.defaultShareInviterName
*/
shareInviterName?: boolean
/**
* How should the sender receive the token.
* (sender only receives a token if no email is provided)
*
* @default options.defaultSenderResponse
*/
senderResponse?: "token" | "url"
/**
* Where should we redirect the user?
* (only if no email is provided)
*
* @default options.defaultSenderResponseRedirect
*/
senderResponseRedirect?: "signUp" | "signIn"
/**
* If inviteUrlType is set to custom, this will be used
* Use {token} and {callbackUrl}, this will be replaced with their values
*/
customInviteUrl?: string
/**
* The url to send in the email to the user
*
* @default api
*/
inviteUrlType?: "api" | "custom"
}
```
Activate Invite [#activate-invite]
```ts
type activateInvite = {
/**
* Where to redirect the user after sing in/up
*/
callbackURL?: string
/**
* The invite token
*/
token: string
}
```
Activate Invite Callback [#activate-invite-callback]
This endpoint is not recommended for direct usage. It exists mainly for internal use (e.g. the default link sent in invite emails).
The email link opens in the browser and triggers a `GET` request to `/api/auth/invite/{token}?callbackURL=...`.
For most apps, you should use [Activate Invite](#activate-invite) instead.
```ts
type activateInviteCallback = {
/**
* Where to redirect the user after sing in/up
*/
callbackURL?: string
/**
* @ignore
* The invite token
*/
token: string
}
```
Reject Invite [#reject-invite]
```ts
type rejectInvite = {
/**
* The invite token to reject.
*/
token: string
}
```
Cancel Invite [#cancel-invite]
```ts
type cancelInvite = {
/**
* The invite token to cancel.
*/
token: string
}
```
Get Invite [#get-invite]
Get basic information about an invitation.
```ts
type getInvite = {
/**
* The invite token to look up.
*/
token: string
}
```
List Invites [#list-invites]
List all (private) invitations for the current user
```ts
type listInvites = {
/**
* The value to search for
*/
searchValue?: string
/**
* The field to search in, defaults to email. Can be `email`, `name` or `domainWhitelist`
*/
searchField?: "name" | "email" | "domainWhitelist"
/**
* The operator to use for the search. Can be `contains`, `starts_with` or `ends_with`
*/
searchOperator?: "contains" | "starts_with" | "ends_with"
/**
* The numbers of invitations to return
*/
limit?: number
/**
* The offset to start from
*/
offset?: number
/**
* The field to sort by
*/
sortBy?: string
/**
* The direction to sort by. Can be `asc` or `desc`
*/
sortDirection?: "asc" | "desc"
/**
* The field to filter by
*/
filterField?: string
/**
* The value to filter by
*/
filterValue?: string | number | boolean
/**
* The operator to use for the filter. Can be `eq`, `ne`, `lt`, `lte`, `gt` or `gte`
*/
filterOperator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte"
}
```
# Core: Flow Diagram
URL: /docs/core/diagram
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/core/diagram.mdx
Step-by-step diagrams showing how the plugin works internally.
---
Invite Creation [#invite-creation]
This diagram shows how an inviter creates an invite, including validation, token generation, and email sending.
To move and zoom the diagram, hold the **Alt** key and use your mouse wheel or click and drag.
Invite Activation [#invite-activation]
This diagram shows the full invite activation flow, including both logged-in and not logged-in users, with validations, role updates, hook execution, and final redirection.
# Core: Extras
URL: /docs/core/extras
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/core/extras.mdx
Additional features and customization options for the invite plugin.
---
Cookie Customization [#cookie-customization]
The invite plugin stores the token in an HTTP-only cookie called `{my_app_name}.invite_token` by default. You can customize cookies globally using Better Auth's `advanced` options.
Cookie Prefix [#cookie-prefix]
Change the default cookie prefix (the app name) with `cookiePrefix`:
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
advanced: {
cookiePrefix: "my-app"
}
});
```
Custom Cookie Name [#custom-cookie-name]
Customize the cookie name for the invite plugin:
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
advanced: {
cookies: {
invite_token: {
name: "custom_invite_token"
}
}
}
});
```
For more details, see [Better Auth Cookies Documentation](https://www.better-auth.com/docs/concepts/cookies).
Schema Customization [#schema-customization]
You can rename tables and fields for the plugin using the `schema` option in the plugin configuration. For example, changing field names or table names:
```ts title="auth.ts"
import { invite } from "better-invite";
const auth = betterAuth({
plugins: {
invite({
schema: {
invite: {
modelName: "custom_invite",
fields: {
token: "custom_token", // token was the original name, but was changed to custom_token
createdAt: "created_at",
expiresAt: "expires_at",
maxUses: "max_uses",
createdByUserId: "creator_id",
redirectToAfterUpgrade: "redirect_url",
shareInviterName: "share_name",
email: "invite_email",
role: "invite_role",
}
},
inviteUse: {
modelName: "customInviteUse",
fields: {
inviteId: "invite_id",
usedAt: "usedAt",
usedByUserId: "used_by_user_id"
}
}
}
})
}
});
```
Fallbacks [#fallbacks]
The invite plugin includes several fallback mechanisms to ensure smooth operation even if certain options are missing or misconfigured.
Here's an example:
* **Token Generator:**\
If you set `tokenType` to `"custom"` but don't provide a `generateToken` function, the plugin falls back to the default `"token"` generator. This ensures that invites are always created with a valid, secure token.
Optional Session [#optional-session]
The `activateInvite` route uses an optional session, which allows invites to be activated whether or not the user is logged in:
* **If you are logged in:**\
The invite is immediately applied, and your role is upgraded according to the invite.
* **If you are not logged in:**\
Before redirecting you to the sign-in/sign-up page, the plugin creates a temporary **invite cookie** in your browser.\
This cookie stores the invite token securely, so once you complete the login or registration process, a hook automatically reads the cookie and upgrades your role.
Rate Limit [#rate-limit]
You can configure rate limiting for the invite plugin routes using the `rateLimit` option in `betterAuth`.
```ts
import { betterAuth } from "better-auth";
export const auth = betterAuth({
//...other options
rateLimit: {
window: 60, // time window in seconds
max: 100, // max requests in the window
customRules: {
"/invite/activate": {
window: 10,
max: 3,
},
"/invite/create": {
window: 10,
max: 3,
},
// ... other custom rules
},
},
})
```
For more details, see [Better Auth Rate Limit Documentation](https://www.better-auth.com/docs/concepts/rate-limit).
# Core: Schema
URL: /docs/core/schema
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/core/schema.mdx
Database schema for the invite plugin.
---
Invite [#invite]
InviteUse [#inviteuse]
# Examples: Full Example
URL: /docs/examples
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/examples/index.mdx
A full working example of a custom invite activation flow with a UI.
---
This section documents an activation flow that is **not available yet**.
The plugin is currently being updated to support this behavior.
Default behavior [#default-behavior]
By default, the URL generated by `sendInvite` points to:
`/api/activate-invite`
This works out of the box, but it has two important limitations:
* It has no UI
* It automatically activates the invite immediately
This is great for simple or headless flows, but some apps want a better user experience (and usually an explicit Accept / Reject step).
***
Custom UI flow [#custom-ui-flow]
A common pattern is to make your `sendInvite` email link point to a page like:
`/activate-invite/${token}`
This page becomes the handoff where you:
* ensure the user is authenticated
* show an Accept / Reject UI
* activate the invite only when the user explicitly accepts
***
Configure a custom invite URL [#configure-a-custom-invite-url]
Option A: Set a default custom invite URL (recommended) [#option-a-set-a-default-custom-invite-url-recommended]
This makes every invite link point to your UI page instead of `/api/activate-invite`.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { admin } from "better-auth/plugins";
import { invite } from "better-invite";
export const auth = betterAuth({
plugins: [
admin({
// ... your admin config
}),
invite({
defaultMaxUses: 1,
defaultRedirectAfterUpgrade: "/auth/invited",
// All invite links will now point to:
// /activate-invite/${token}
defaultCustomInviteUrl: "/activate-invite",
async sendUserInvitation({ email, role, url, token, newAccount }) {
// `url` uses your custom invite URL and automatically appends the token: /activate-invite/${token}
if (newAccount) {
await sendInvitationEmail(role, email, url);
return;
}
await sendRoleUpgradeEmail(role, email, url);
},
}),
],
});
```
Option B: Override the URL for only one invite [#option-b-override-the-url-for-only-one-invite]
Useful when only specific invites need a custom UI.
```ts
await authClient.invite.create({
email: "person@example.com",
role: "admin",
customInviteUrl: "/activate-invite",
});
```
***
Recommended activation flow [#recommended-activation-flow]
Once the user lands on:
`/activate-invite/[token]`
You can implement the following flow.
1\) User clicks the invite link [#1-user-clicks-the-invite-link]
Your invite email contains something like:
* `/activate-invite/INVITE_TOKEN_HERE`
2\) Check if the user is logged in [#2-check-if-the-user-is-logged-in]
When the page loads, verify whether the visitor is authenticated.
If the user is not logged in, redirect them to login with a callback URL:
```ts
authClient.signIn({
callbackUrl: `/activate-invite/${token}`,
});
```
After login, they will return to the same invite page.
3\) Show an Accept / Reject UI [#3-show-an-accept--reject-ui]
Once the user is authenticated, show a simple UI:
* Accept invite
* Reject invite
4\) If they accept, activate the invite [#4-if-they-accept-activate-the-invite]
```ts
await authClient.invite.activate({ token });
```
5\) If they reject, reject the invite [#5-if-they-reject-reject-the-invite]
If the user clicks Reject, you should call:
```ts
await authClient.invite.reject({ token });
```
`{/*! Fix this part */}`
6\) Redirect after the action [#6-redirect-after-the-action]
After activating or rejecting, redirect the user to wherever makes sense for your app (for example, a dashboard, an onboarding page, or a simple "Invite handled" page).
***
Full example (Next.js App Router) [#full-example-nextjs-app-router]
Below is a complete example for a real `/activate-invite/[token]` page.
It:
* checks authentication on the server
* redirects to login if needed
* shows an Accept / Reject UI
* activates the invite only when accepted
* redirects after activation
```tsx title="app/activate-invite/[token]/page.tsx"
import { redirect } from "next/navigation";
import Link from "next/link";
import { auth } from "@/lib/auth";
import ActivateInviteClient from "./ui";
export default async function ActivateInvitePage({
params,
}: {
params: { token: string };
}) {
const session = await auth.api.getSession();
const token = params.token;
// Not logged in: redirect to login and return here after authentication
if (!session?.user) {
redirect(`/login?callbackUrl=/activate-invite/${token}`);
}
return (
You have been invited
Accepting this invite will activate it and apply the role or membership
configured by the sender.
Not you?{" "}
Sign out
);
}
```
```tsx title="app/activate-invite/[token]/ui.tsx"
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { authClient } from "@/lib/auth-client";
export default function ActivateInviteClient({ token }: { token: string }) {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
async function acceptInvite() {
setLoading(true);
setError(null);
try {
const res = await authClient.invite.activate({ token });
// If your plugin returns a redirect URL, prefer it.
const redirectTo =
(res as any)?.redirectTo ??
(res as any)?.data?.redirectTo ??
"/auth/invited";
router.push(redirectTo);
router.refresh();
} catch (err: any) {
setError(err?.message ?? "Failed to activate invite.");
setLoading(false);
}
}
function rejectInvite() {
router.push("/");
}
return (
{error ?
{error}
: null}
);
}
```
***
Notes [#notes]
* The default `/api/activate-invite` route is intentionally minimal and does not provide a UI.
* A custom UI flow is recommended for onboarding, role upgrades, and multi-workspace apps.
# Reference: Contributing
URL: /docs/reference/contributing
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/reference/contributing.mdx
Guidelines for contributing to the invite plugin documentation.
---
Contributing to Better Invite [#contributing-to-better-invite]
This plugin is actively maintained and evolving, and contributions from the community are highly welcome! Whether you're reporting a bug, suggesting a new feature, improving the documentation, or adding examples, your help makes the plugin better for everyone.
Ways You Can Contribute: [#ways-you-can-contribute]
* **Report Issues:**\
If you encounter a bug, unexpected behavior, or have trouble using the plugin, open an issue on GitHub. Clear and detailed bug reports are incredibly valuable.
* **Suggest Features:**\
Have an idea for a new feature, improvement, or enhancement? Open an issue to propose it. Even small suggestions help shape the plugin's roadmap.
* **Share Edge Cases:**\
If you discover unusual scenarios or workflows where the plugin behaves differently than expected, sharing these cases helps improve its reliability.
* **Improve Documentation:**\
Contributions to the README, guides, examples, or documentation are welcome. Clear, up-to-date documentation helps everyone get started faster.
* **Submit Pull Requests (PRs):**\
If you can fix a bug, add a feature, or improve the docs, submit a PR. Even small fixes, like typos, formatting, or code examples, are valuable.
* **Provide Feedback:**\
Your feedback on usability, developer experience, or real-world usage scenarios is very helpful. It helps guide future updates and ensures the plugin meets the community's needs.
Guidelines for Submitting Contributions [#guidelines-for-submitting-contributions]
Fork the repository and create a branch for your changes.
Follow existing code style and documentation patterns.
Test your changes locally.
Provide a clear description for your issue or PR.
After submitting, be patient while maintainers review and merge your changes.
Even small contributions make a difference! By helping improve the plugin or its documentation, you're supporting Better Auth users worldwide.
***
***
Thank you for considering contributing! Your input helps make Better Invite more reliable, flexible, and user-friendly.
# Reference: FAQ
URL: /docs/reference/faq
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/reference/faq.mdx
Frequently Asked Questions about the invite plugin.
---
I originally needed an easy way to grant roles without having to:
* Manually edit the database
* Build a full admin UI
* Write one-off scripts
I also wanted a solution that:
* Fits naturally into Better Auth's plugin system
* Doesn't assume a specific permission model
* Remains flexible for real-world applications
After implementing it for my own project, I realized it could be useful for other Better Auth users, so I packaged it as a plugin.
No. This is a community-maintained plugin and is not officially affiliated with Better Auth.
The plugin is maintained by
When an invite is activated, a hook runs after authentication to validate the
token. If valid, the plugin automatically upgrades the user's role according
to the invite and marks the invite as used.
No. The plugin integrates seamlessly with the default sign-in and sign-up
flows using hooks, so no extra logic is required.
Yes. Each invite has a maxUses property. Once the maximum number
of uses is reached, the invite becomes invalid.
Yes. You can create **public invites** that generate a token or URL. Anyone
with this token can use the invite without needing an email.
If the invited user already has an account, the invite will be treated as a
**role upgrade**. If the user does not exist, they can use the invite to
create a new account.
Invites use tokens stored in HTTP-only cookies, which prevents client-side
access. Email-specific invites can only be used by the intended recipient.
Tokens can expire, and usage limits prevent abuse.
Yes! You can use the default token, a shorter human-readable code, or provide
a custom token generator using the generateToken option.
No. For private invites sent via email, the link automatically activates the
invite. However, you can use the activateInvite method if you
prefer manual activation.
Absolutely. You can use the canCreateInvite function to restrict
who can create invites, and the canAcceptInvite function to
control who can accept them. This allows you to enforce role hierarchies and
other business rules.
The plugin was originally named `better-auth-invite-plugin`. But later
in version 0.5 it was renamed to `better-invite`, because it's a
smaller name and easier to remember
# Reference: Security
URL: /docs/reference/security
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/reference/security.mdx
Security considerations for the invite plugin.
---
Better Invite is designed with security in mind to protect invitations, tokens, and role upgrades. Below are the main mechanisms and best practices.
Token Security [#token-security]
* **Unique Tokens:** Each invite uses a unique token to prevent guessing.
* **Types of Tokens:**
* **Token:** Long, random string (default, recommended for email and URL invites).
* **Code:** Short, human-readable code (easier to share but less secure).
* **Custom:** Fully customizable token generator via `generateToken`.
* **HTTP-Only Cookies:** Tokens are stored in HTTP-only cookies to prevent access from client-side scripts.
* **Expiration:** Tokens can expire (`invitationTokenExpiresIn`), limiting the window for misuse.
Permissions and Access Control [#permissions-and-access-control]
* **Creating Invites:** By default, users can create invites. This can be restricted with `canCreateInvite` to prevent role escalation.
* **Accepting Invites:** Default behavior allows invite acceptance, but `canAcceptInvite` can enforce rules such as only allowing new account creation or restricting certain roles.
Usage Limits [#usage-limits]
* Each invite has a `maxUses` property to prevent unlimited sharing.
* Expired or fully used invites are automatically rejected.
Secure Delivery [#secure-delivery]
* **Private Invites:** When an invite is sent via email, only that email can activate it.
* **Public Invites:** Anyone with the token can use it, but usage limits and expiration still apply.
Best Practices [#best-practices]
* Prefer the default **Token** type for email invites for maximum security.
* Configure `canCreateInvite` and `canAcceptInvite` to prevent role abuse.
* Never log or expose invite tokens on the client side.
* Monitor token usage and expiration to avoid old invites being exploited.
Reporting Vulnerabilities [#reporting-vulnerabilities]
If you discover a vulnerability in Better Invite, please report it to us at [security@better-invite.com](mailto:security@better-invite.com).
All reports are addressed promptly, and validated discoveries will receive credit. Please include as much detail as possible (steps to reproduce, affected versions, etc.) to help us investigate efficiently.
# Reference: Troubleshooting
URL: /docs/reference/troubleshooting
Source: https://raw.githubusercontent.com/better-invite/better-invite/refs/heads/main/docs/content/docs/reference/troubleshooting.mdx
Guidelines for contributing to the invite plugin documentation.
---
`Export expireCookie doesn't exist in target module` [#export-expirecookie-doesnt-exist-in-target-module]
If you see an error like:
```ts
./node_modules/.pnpm/better-invite@.../dist/hooks.js:2:1
Export expireCookie doesn't exist in target module
1 | import { createAuthMiddleware } from "better-auth/api";
> 2 | import { expireCookie } from "better-auth/cookies";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
The export expireCookie was not found in module .../better-auth/dist/cookies/index.mjs
```
Why this happens [#why-this-happens]
The plugin relies on the `expireCookie` export, which was introduced in Better Auth v1.4.13.
If you're using an older version of Better Auth, this export won't be available, leading to the error.
(see [PR #7363 in Better Auth](https://github.com/better-auth/better-auth/pull/7363/changes/243b0c728aa34dcb5d1200f25ea707a1c2d59709#diff-f528705ea9dfb694ce74a60f2e2b4d204f2130b2122aad677a5f99241c968329R300))
Solution [#solution]
Upgrade Better Auth to v1.4.13 or newer
npm
pnpm
yarn
bun
```bash
npm install better-auth@latest
```
```bash
pnpm add better-auth@latest
```
```bash
yarn add better-auth@latest
```
```bash
bun add better-auth@latest
```