A complete waitlist for your Better Auth app. People join, follow their place in line, bring friends with their referral link, and get an invite when you let them in.
- A real queue: every entry has a live position, and you can boost people up the line.
- Status links, emailed on join, showing the person's place in line and their share link.
- Referrals with share links, self-referral detection and a success callback for your rewards.
- Invites: approve people one by one or "next 50", or invite someone directly. Each invite is a single-use code bound to one email, with an expiry.
- Manager tools to list, search, approve, reject, requeue, boost and delete entries, gated by the admin plugin's roles or your own check.
- Invite-only sign-up while the waitlist is on, across every sign-up method (email and password, OAuth, One Tap, magic link, email OTP, anonymous). Existing users keep signing in as usual.
- No email enumeration: join, refusals and timing look the same whether an email is new, pending, rejected or already a user.
- Typed, tiny client (about 2 kB gzipped). No server code, zod or
better-callreaches the browser. - Adapter-agnostic, ESM only, Better Auth
^1.7.0.
Coming from 3.x? v4 is a rewrite: read Upgrading from v3.
pnpm add better-auth-waitlist
# or: npm install better-auth-waitlist / yarn add better-auth-waitlist / bun add better-auth-waitlistbetter-auth and @better-auth/core (^1.7.0) are peer dependencies.
// auth.ts
import { betterAuth } from "better-auth"
import { admin } from "better-auth/plugins"
import { waitlist } from "better-auth-waitlist"
export const auth = betterAuth({
// ...database, emailAndPassword, socialProviders
plugins: [
admin(), // admins are waitlist managers by default
waitlist({
statusLink: { buildUrl: (token) => `https://example.com/waitlist?token=${token}` },
sendStatusLink: async ({ email, url }) => {
await sendEmail(email, "You're on the waitlist", `Check your place in line: ${url}`)
},
invite: { buildUrl: (code) => `https://example.com/register?code=${code}` },
sendInvite: async ({ email, url }) => {
await sendEmail(email, "You're invited", `Create your account: ${url}`)
},
}),
],
// Send emails after the response, so timing doesn't reveal who is on the list.
advanced: { backgroundTasks: { handler: (promise) => waitUntil(promise) } },
})The plugin adds a waitlistEntry model. Generate or apply it with the Better Auth CLI, as for any plugin.
// auth-client.ts
import { createAuthClient } from "better-auth/client"
import { waitlistClient } from "better-auth-waitlist/client"
export const authClient = createAuthClient({ plugins: [waitlistClient()] })The plugin has no UI. Your app calls the client from three places:
- Join:
authClient.waitlist.join({ email })adds the email and sends the status link. - Status link:
confirmandstatuswith the link's token show the person's place in line and their share link. - Invite link:
invite.accept({ code })checks the code; then any Better Auth sign-up method creates the account for the invited email.
Approve people with authClient.waitlist.admin.approve(...) from your admin UI, or auth.api.approveWaitlistEntries(...) from server code. Sign-up is now invite-only.
See Building the pages for complete examples.
- Building the pages: join, status and register pages.
- How it works: entry statuses and transitions.
- Managing the waitlist: managers, approving, invites.
- The sign-up gate: admission, refusals per sign-up method, bypasses, Guests.
- Referrals: share links, success callback, rewards.
- API reference: every endpoint's parameters, response and errors, the entry type and error codes.
- Client API: every endpoint, helpers, error codes, extra join fields.
- Options: every option and callback with its default.
- Production checklist
- Upgrading from v3
See CONTRIBUTING.md.