Upgrade Anonymous Plugin
@kardoe/better-auth-upgrade-anonymous — Convert anonymous users to full accounts.
@kardoe/better-auth-upgrade-anonymous provides a single endpoint to convert anonymous users into full authenticated users. It flips the isAnonymous flag, optionally updates email and name, and refreshes the session — no re-authentication required.
Installation
npm install @kardoe/better-auth-upgrade-anonymousConfiguration
Enable both the anonymous and upgradeAnonymous plugins:
auth: defineAuth("better-auth", {
plugins: ["anonymous", "upgradeAnonymous"],
})The plugin accepts optional configuration:
import { upgradeAnonymous } from "@kardoe/better-auth-upgrade-anonymous";
upgradeAnonymous({
emailConfigured: true, // Whether email delivery is available
requireEmailVerification: true, // Whether to require email verification
})| Option | Default | Description |
|---|---|---|
emailConfigured | false | When true and an email is provided, emailVerified is set based on requireEmailVerification |
requireEmailVerification | true | When true, provided emails are marked unverified (requiring OTP). When false, emails are marked verified immediately |
Quickback Compiler
When using the Quickback compiler, these options are set automatically based on your SES environment variables. You don't need to configure them manually.
Endpoint
POST /auth/v1/upgrade-anonymousFlow
- Validates the user's session (returns
401if not authenticated) - Checks if the user is already upgraded (returns success immediately if so)
- If an email is provided, checks for duplicates (returns
400if email is taken) - Updates
isAnonymoustofalse, plus optionalemail,name, andemailVerifiedfields - Refreshes the session cookie with the updated user object
- Returns the updated user, session, and
verificationRequiredflag
Request
The body is optional. When provided, it accepts:
| Field | Type | Description |
|---|---|---|
email | string (email) | Optional. Real email to replace the anonymous placeholder |
name | string | Optional. User's display name |
# Minimal — just upgrade, no email/name
curl -X POST https://api.example.com/auth/v1/upgrade-anonymous \
-H "Cookie: better-auth.session_token=..."
# With email and name
curl -X POST https://api.example.com/auth/v1/upgrade-anonymous \
-H "Cookie: better-auth.session_token=..." \
-H "Content-Type: application/json" \
-d '{"email": "jane@example.com", "name": "Jane"}'Response
{
"success": true,
"verificationRequired": true,
"user": {
"id": "usr_abc123",
"email": "jane@example.com",
"isAnonymous": false
},
"session": {
"id": "sess_xyz",
"userId": "usr_abc123"
}
}The verificationRequired field is true when all three conditions are met:
- An email was provided in the request body
emailConfiguredistruein plugin configrequireEmailVerificationistruein plugin config
When verificationRequired is true, the frontend should send a verification OTP and redirect the user to verify their email.
How It Works
The plugin is designed to be flexible:
- Minimal by default — With no body, only changes
isAnonymousfromtruetofalse - Optional email/name — Can update email and name in the same request
- Duplicate protection — Rejects emails already associated with another account
- Verification-aware — Returns
verificationRequiredso the frontend knows whether to trigger OTP - Session preserved — Same session ID, no re-authentication needed
- Idempotent — Calling on an already-upgraded user returns success immediately
Typical Flows
Passkey Signup (no email)
- User creates anonymous session
- User registers a passkey
- App calls
POST /auth/v1/upgrade-anonymouswith no body - User is now a full user with passkey auth — no email on file
Passkey Signup (with email)
- User creates anonymous session
- User registers a passkey
- User optionally enters email/name on the email collection step
- App calls
POST /auth/v1/upgrade-anonymouswith{ email, name } - If
verificationRequired: app sends OTP, user verifies, then redirects to dashboard - Otherwise: user goes straight to dashboard
Email Signup
- User starts as anonymous (created via Better Auth's
anonymousplugin) - User provides email/password through your UI (via combo auth or signup)
- Your app calls
POST /auth/v1/upgrade-anonymous - User is now a full user with the same ID and all their data intact
Client Plugin
import { upgradeAnonymousClient } from "@kardoe/better-auth-upgrade-anonymous/client";
const authClient = createAuthClient({
plugins: [upgradeAnonymousClient()],
});
// Upgrade the current anonymous user
await authClient.upgradeAnonymous();See Also
- Combo Auth Plugin — Combined magic link + OTP for collecting credentials
- Auth Configuration — Configuring auth plugins