Quickback Docs

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-anonymous

Configuration

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
})
OptionDefaultDescription
emailConfiguredfalseWhen true and an email is provided, emailVerified is set based on requireEmailVerification
requireEmailVerificationtrueWhen 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-anonymous

Flow

  1. Validates the user's session (returns 401 if not authenticated)
  2. Checks if the user is already upgraded (returns success immediately if so)
  3. If an email is provided, checks for duplicates (returns 400 if email is taken)
  4. Updates isAnonymous to false, plus optional email, name, and emailVerified fields
  5. Refreshes the session cookie with the updated user object
  6. Returns the updated user, session, and verificationRequired flag

Request

The body is optional. When provided, it accepts:

FieldTypeDescription
emailstring (email)Optional. Real email to replace the anonymous placeholder
namestringOptional. 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
  • emailConfigured is true in plugin config
  • requireEmailVerification is true in 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 isAnonymous from true to false
  • Optional email/name — Can update email and name in the same request
  • Duplicate protection — Rejects emails already associated with another account
  • Verification-aware — Returns verificationRequired so 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)

  1. User creates anonymous session
  2. User registers a passkey
  3. App calls POST /auth/v1/upgrade-anonymous with no body
  4. User is now a full user with passkey auth — no email on file

Passkey Signup (with email)

  1. User creates anonymous session
  2. User registers a passkey
  3. User optionally enters email/name on the email collection step
  4. App calls POST /auth/v1/upgrade-anonymous with { email, name }
  5. If verificationRequired: app sends OTP, user verifies, then redirects to dashboard
  6. Otherwise: user goes straight to dashboard

Email Signup

  1. User starts as anonymous (created via Better Auth's anonymous plugin)
  2. User provides email/password through your UI (via combo auth or signup)
  3. Your app calls POST /auth/v1/upgrade-anonymous
  4. 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

On this page