Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.

Commit cd52f0f

Browse files
committed
add: app-invite
1 parent c9c4be5 commit cd52f0f

File tree

19 files changed

+2116
-166
lines changed

19 files changed

+2116
-166
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
---
2+
title: App Invite
3+
description: Invite users to your application and allow them to sign up.
4+
---
5+
6+
<StatsBadge npmPackage="@better-auth-kit/app-invite" />
7+
<GithubButton url="https://github.com/ping-maxwell/better-auth-kit/tree/main/packages/plugins/app-invite" />
8+
<NpmButton url="https://www.npmjs.com/package/@better-auth-kit/app-invite" />
9+
10+
The App Invite plugin enables you to invite users to your application through email invitations. It supports two types of invitations:
11+
12+
- **Personal Invitations**: Targeted to specific email addresses, ensuring only the intended recipient can use the invitation
13+
- **Public Invitations**: Can be used by multiple users, making it ideal for open sign-up scenarios
14+
15+
## Installation
16+
17+
<Steps>
18+
<Step>
19+
### Add the plugin to your auth config
20+
21+
To use the App Invite plugin, add it to your auth config.
22+
23+
```ts title="auth.ts"
24+
import { betterAuth } from "better-auth"
25+
import { appInvite } from "better-auth/plugins" // [!code highlight]
26+
27+
export const auth = betterAuth({
28+
// ... other config options
29+
plugins: [
30+
appInvite({ // [!code highlight]
31+
// required for personal invites // [!code highlight]
32+
sendInvitationEmail: (data) => { // [!code highlight]
33+
// ... send invitation to the user // [!code highlight]
34+
} // [!code highlight]
35+
}) // [!code highlight]
36+
]
37+
})
38+
```
39+
40+
</Step>
41+
<Step>
42+
### Add the client plugin
43+
44+
Include the App Invite client plugin in your authentication client instance.
45+
46+
```ts title="auth-client.ts"
47+
import { createAuthClient } from "better-auth/client"
48+
import { appInviteClient } from "better-auth/client/plugins" // [!code highlight]
49+
50+
const authClient = createAuthClient({
51+
plugins: [
52+
appInviteClient() // [!code highlight]
53+
]
54+
})
55+
```
56+
57+
</Step>
58+
59+
<Step>
60+
### Run migrations
61+
62+
This plugin adds an additional table to the database. [Click here to see the schema](#schema)
63+
64+
```package-install
65+
npx @better-auth/cli migrate
66+
```
67+
68+
or generate
69+
70+
```package-install
71+
npx @better-auth/cli generate
72+
```
73+
74+
</Step>
75+
</Steps>
76+
77+
## Usage
78+
79+
To add members to the application, we first need to send an invitation to the user.
80+
The user will receive an email with the invitation link. Once the user accepts the invitation, they will be signed up to the application.
81+
82+
### Setup Invitation Email
83+
84+
For personal invites to work we first need to provide `sendInvitationEmail` to the `better-auth` instance.
85+
This function is responsible for sending the invitation email to the user.
86+
87+
You'll need to construct and send the invitation link to the user. The link should include the invitation ID,
88+
which will be used with the acceptInvitation function when the user clicks on it.
89+
90+
This is only required for personal invites. Sharing public invitations is up to the inviter.
91+
92+
```ts title="auth.ts"
93+
import { betterAuth } from "better-auth";
94+
import { appInvite } from "better-auth/plugins";
95+
import { sendAppInvitation } from "./email";
96+
97+
export const auth = betterAuth({
98+
plugins: [
99+
appInvite({
100+
async sendInvitationEmail(data) {
101+
const inviteLink = `https://example.com/accept-invitation/${data.id}`;
102+
sendAppInvitation({
103+
name: data.name,
104+
email: data.email,
105+
invitedByUsername: data.inviter.name,
106+
invitedByEmail: data.inviter.email,
107+
inviteLink,
108+
});
109+
},
110+
}),
111+
],
112+
});
113+
```
114+
115+
### Send Invitation
116+
117+
To invite users to the app, you can use the `invite` function provided by the client.
118+
the `invite` function takes an object with the following properties:
119+
120+
- `email`: The email address of the user to invite. Leave empty to create a public invitation.
121+
122+
- `resend`: A boolean value that determines whether to resend the invitation email,
123+
if the user is already invited. Defaults to `false`
124+
125+
- `domainWhitelist`: An optional comma separated list of domains that allows public invitations
126+
to be accepted only from approved domains.
127+
128+
```ts title="invitation.ts"
129+
await authClient.inviteUser({
130+
email: "test@email.com",
131+
});
132+
```
133+
134+
### Accept Invitation
135+
136+
When a user receives an invitation email, they can click on the invitation link to accept the invitation.
137+
The link should include the invitation ID, which will be used to accept the invitation.
138+
139+
```ts title="auth-client.ts"
140+
await authClient.acceptInvitation({
141+
invitationId: "invitation-id",
142+
name: "John Doe", // overridden if predefined in the invitation
143+
email: "test@email.com", // must be set for public invites
144+
password: "password123456",
145+
// ... additional user data
146+
});
147+
```
148+
149+
### Update Invitation Status
150+
151+
To update the status of invitations you can use the `acceptInvitation`, `rejectInvitation`, `cancelInvitation`
152+
function provided by the client. The functions take the invitation id as an argument.
153+
154+
```ts title="auth-client.ts"
155+
// cancel invitation (by default only the inviter can cancel invitations)
156+
await authClient.cancelInvitation({
157+
invitationId: "invitation-id",
158+
});
159+
160+
// reject invitation (this is only available for personal invites)
161+
await authClient.rejectInvitation({
162+
invitationId: "invitation-id",
163+
});
164+
```
165+
166+
### Get Invitation
167+
168+
To get an invitation you can use the `getAppInvitation` function provided by the client. You need to provide the
169+
invitation id as a query parameter.
170+
171+
```ts title="auth-client"
172+
await authClient.getAppInvitation({
173+
query: {
174+
id: params.id,
175+
},
176+
});
177+
```
178+
179+
### List Invitations
180+
181+
Allows an user to list all invitations issued by themself.
182+
183+
```ts title="auth-client"
184+
await authClient.listInvitations({
185+
query: {
186+
limit: 10,
187+
},
188+
});
189+
```
190+
191+
By default 100 invitations are returned. You can adjust the limit and offset using the following query parameters:
192+
193+
- `searchField`: The field to search on, which can be `email`, `name` or `domainWhitelist`.
194+
- `searchOperator`: The operator to use for the search. It can be `contains`, `starts_with`, or `ends_with`.
195+
- `searchValue`: The value to search for.
196+
- `limit`: The number of invitations to return.
197+
- `offset`: The number of invitations to skip.
198+
- `sortBy`: The field to sort the invitations by.
199+
- `sortDirection`: The direction to sort the invitations by. Defaults to `asc`.
200+
- `filterField`: The field to filter the invitations by.
201+
- `filterOperator`: The operator to use for the filter. It can be `eq`, `ne`, `lt`, `lte`, `gt` or `gte`.
202+
- `filterValue`: The value to filter the invitations by.
203+
204+
```ts title="Example"
205+
await authClient.listInvitations({
206+
query: {
207+
searchField: "domainWhitelist",
208+
searchOperator: "contains",
209+
searchValue: "example.com",
210+
limit: 10,
211+
offset: 0,
212+
sortBy: "createdAt",
213+
sortDirection: "desc",
214+
filterField: "expiresAt",
215+
filterOperator: "lt",
216+
filterValue: new Date().toISOString(),
217+
},
218+
});
219+
```
220+
221+
## Schema
222+
223+
224+
225+
The plugin requires an additional table in the database.
226+
227+
Table Name: `appInvitation`
228+
229+
<SchemaDemo plugin="appInvite" includeDefaultUser focus="appInvitation" schema={{
230+
appInvitation: {
231+
fields: {
232+
name: {
233+
type: "string",
234+
required: false,
235+
},
236+
email: {
237+
type: "string",
238+
required: false,
239+
},
240+
inviterId: {
241+
type: "string",
242+
required: true,
243+
references: {
244+
model: "user",
245+
field: "id",
246+
}
247+
},
248+
status: {
249+
type: "string",
250+
required: true,
251+
},
252+
domainWhitelist: {
253+
type: "string",
254+
required: true
255+
},
256+
expiresAt: {
257+
type: "date",
258+
required: true
259+
},
260+
createdAt: {
261+
type: "date",
262+
required: true,
263+
},
264+
}
265+
}
266+
}} />
267+
268+
Fields:
269+
270+
- `id`: Unique identifier for each invitation
271+
- `name`: The name of the user
272+
- `email`: The email address of the user
273+
- `inviterId`: The ID of the inviter
274+
- `status`: The status of the invitation
275+
- `domainWhitelist`: A comma separated whitelist of domains for a public invitation
276+
- `expiresAt`: Timestamp of when the invitation expires
277+
- `createdAt`: Timestamp of when the invitation was created
278+
279+
280+
## Options
281+
282+
**allowUserToCreateInvitation**: `boolean` | `((user: User, type: "personal" | "public") => Promise<boolean> | boolean)` - A function that determines whether a user can create invite others. By defaults it's `true`. You can set it to `false` to restrict users from creating invitations.
283+
284+
**allowUserToCancelInvitation**: `(data: { user: User, invitation: AppInvitation }) => Promise<boolean> | boolean` - A functon that determines whether a user can cancel inivtations. By default the user can only cancel invites created by them. You can set it to `false` to restrict users from canceling invitations.
285+
286+
**invitationExpiresIn**: `number` - How long the invitation link is valid for in seconds. By default, it's 48 hours (2 days).
287+
288+
**sendInvitationEmail**: `async (data) => Promise<void>` - A function that sends an invitation email to the user. This is only required for personal invitations.
289+
290+
**autoSignIn**: A boolean value that determines whether to prevent automatic sign-up when accepting an invitation. Defaults to `false`.
291+
292+
**$Infer.AdditionalFields**: Allows you to infer additional data for the user. This option is available in the client plugin aswell.

apps/docs/src/components/sidebar-content.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
Brain,
2222
Book,
2323
User,
24+
UserPlus,
2425
} from "lucide-react";
2526
import type { Content } from "./sidebar";
2627

@@ -67,6 +68,11 @@ export const contents: Content[] = [
6768
title: "Legal Consent",
6869
icon: () => <Scale size={16} />,
6970
},
71+
{
72+
href: "/docs/plugins/app-invite",
73+
title: "App Invite",
74+
icon: () => <UserPlus size={16} />,
75+
},
7076
{
7177
href: "/docs/plugins/blockade",
7278
title: "Blockade",

0 commit comments

Comments
 (0)