# Upgrade Anonymous Plugin

`@quickback-dev/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

```bash
npm install @quickback-dev/better-auth-upgrade-anonymous
```

## Configuration

Enable both the `anonymous` and `upgradeAnonymous` plugins:

```typescript
auth: defineAuth("better-auth", {
  plugins: ["anonymous", "upgradeAnonymous"],
})
```

The plugin accepts optional configuration:

```typescript
import { upgradeAnonymous } from "@quickback-dev/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-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:

| Field | Type | Description |
|-------|------|-------------|
| `email` | `string` (email) | Optional. Real email to replace the anonymous placeholder |
| `name` | `string` | Optional. User's display name |

```bash
# 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

```json
{
  "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

```typescript
import { upgradeAnonymousClient } from "@quickback-dev/better-auth-upgrade-anonymous/client";

const authClient = createAuthClient({
  plugins: [upgradeAnonymousClient()],
});

// Upgrade the current anonymous user
await authClient.upgradeAnonymous();
```

## See Also

- [Combo Auth Plugin](/stack/auth/plugins/combo-auth) — Combined magic link + OTP for collecting credentials
- [Auth Configuration](/compiler/config/providers) — Configuring auth plugins
