# Feature Flags

Control which features are available in your Account UI deployment using feature flags. All flags are boolean environment variables and **all of them carry the `VITE_` prefix** — Vite only exposes `VITE_*` vars to client code, so an unprefixed `ENABLE_PASSKEY=true` is silently ignored.

> **Where these come from**
>
> Resolution order for every flag is: worker-injected `window.__QUICKBACK_RUNTIME` blob → baked `VITE_*` env → built-in default. When a Quickback worker serves the Account UI it injects your `quickback.config.ts` `account.auth` settings into the blob, so you normally set these in the config rather than in a `.env` file. See [With Quickback](/ui/account/with-quickback).


## Reference

### `appConfig.features`

These ten flags populate `appConfig.features` and are the ones `isFeatureEnabled()` accepts.

| Variable | `features` key | Default |
|---|---|---|
| `VITE_ENABLE_ORGANIZATIONS` | `organizations` | `false` |
| `VITE_ENABLE_ORG_TEAMS` | `orgTeams` | `false` |
| `VITE_ENABLE_ADMIN` | `admin` | `true` |
| `VITE_ENABLE_FILE_UPLOADS` | `fileUploads` | `false` |
| `VITE_ENABLE_PASSKEY` | `passkey` | `false` |
| `VITE_ENABLE_EMAIL_OTP` | `emailOTP` | `false` |
| `VITE_ENABLE_PASSWORD` | `password` | `true` |
| `VITE_ENABLE_GOOGLE_OAUTH` | `googleOAuth` | `false` |
| `VITE_ENABLE_SUBSCRIPTIONS` | `subscriptions` | `false` |
| `VITE_ENABLE_REALTIME` | `realtime` | `false` |

### `appConfig.auth`

Three further flags configure the signup and email flows. They live on `appConfig.auth`, **not** on `appConfig.features` — `isFeatureEnabled()` does not accept them.

| Variable | `auth` key | Default |
|---|---|---|
| `VITE_ENABLE_SIGNUP` | `enableSignup` | `true` |
| `VITE_ENABLE_EMAIL_VERIFICATION` | `requireEmailVerification` | `true` |
| `VITE_DISABLE_EMAIL_STATUS_CHECK` | `disableEmailStatusCheck` | `false` |

> **Password is on, passkey and OTP are off**
>
> The out-of-the-box posture is email + password only. Passkey, email OTP, organizations, and Google OAuth all default to `false` — you opt in. Admin defaults to `true`.


> **These are the SPA's built-in defaults — the compiler overrides most of them**
>
> The defaults above are what the Account SPA falls back to when nothing else sets a flag. In a **compiled** Quickback project the compiler derives the flags from your Better Auth config and `account.auth` shorthand, and only emits a `VITE_*` value when it has one — an unset flag emits nothing and the built-in default applies.
>
> Two derivations are worth knowing because they do not match the built-in default:
>
> - **`organizations` resolves to `true`** unless you set `account.auth.organizations: false`, so a compiled project has organizations on even though the SPA default is `false`.
> - **`admin` is emitted only when you set `account.auth.admin`.** Leave it unset and the compiler strips the `/admin` route files from the bundle while the SPA's built-in `admin: true` default still reports the feature as enabled. Set `account.auth.admin` explicitly — in either direction — rather than relying on the default.


## Authentication Features

### User Signup

```bash
VITE_ENABLE_SIGNUP=true  # default: true
```

Controls whether new users can register.

**When enabled:**
- Shows "Sign Up" link on login page
- `/signup` route is accessible
- New users can create accounts

**When disabled:**
- Signup route returns 404
- Only existing users can log in
- Useful for invite-only applications

### Email Verification

```bash
VITE_ENABLE_EMAIL_VERIFICATION=true  # default: true
```

Controls the email verification flow.

**When enabled:**
- Users must verify email before full access
- Verification email sent on signup
- "Resend verification email" option available
- Unverified users see verification prompt

**When disabled:**
- Email addresses are trusted without verification
- Users have immediate access after signup

> **Security Consideration**
>
> Disabling email verification can allow fake email addresses. Only disable if you have another verification mechanism.


### Email Deliverability Check

```bash
VITE_DISABLE_EMAIL_STATUS_CHECK=false  # default: false
```

Note the inverted sense — this flag *disables* a check that is on by default.

**When `false` (checking enabled):**
- System validates email addresses are deliverable
- Rejects disposable/temporary email providers
- Prevents typos in domain names

**When `true` (checking disabled):**
- Accepts all email formats
- Useful for development/testing
- Allows `@test.com`, `@localhost`, etc.

### Password Authentication

```bash
VITE_ENABLE_PASSWORD=true  # default: true
```

**When enabled:**
- Email + password fields on login page
- Password field on signup page
- Traditional username/password authentication

**When disabled:**
- No password fields shown
- Users authenticate via email OTP or passkey

### Passkey Login

```bash
VITE_ENABLE_PASSKEY=false  # default: false
```

Controls passkey LOGIN only (not signup). When enabled, users can authenticate using passkeys.

**When enabled:**
- Users can register passkeys (fingerprint, Face ID, hardware keys)
- Passwordless login option
- "Manage Passkeys" page available

**When disabled:**
- No passkey registration
- No passkey login option

**Requirements:**
- HTTPS (passkeys require secure context)
- Modern browser with WebAuthn support

### Email OTP

```bash
VITE_ENABLE_EMAIL_OTP=false  # default: false
```

**When enabled:**
- Users can receive one-time passwords via email
- Alternative to password login
- `/email-otp` route available

**When disabled:**
- No email OTP option
- Password or passkey required

### Google OAuth

```bash
VITE_ENABLE_GOOGLE_OAUTH=false  # default: false
```

**When enabled:**
- "Continue with Google" button on the login and signup pages
- Google row on `/connected-accounts` (linked from **Manage Connected Accounts** on `/profile`)

**When disabled:**
- No social sign-in button, no **Manage Connected Accounts** link, no `/connected-accounts` page

**Requirements:**
- Google OAuth credentials configured on the Better Auth side

### Cloudflare OAuth

```bash
VITE_ENABLE_CLOUDFLARE_OAUTH=false  # default: false
```

**When enabled:**
- "Continue with Cloudflare" button on the login and signup pages
- Cloudflare rows on `/connected-accounts` — unlike
  Google, more than one Cloudflare identity can be linked to the same user
  (**Connect another Cloudflare account**), and every linked identity signs
  in to this user. Link work and personal Cloudflare logins here so each one
  doesn't mint its own separate account.

**When disabled:**
- No Cloudflare sign-in button, no **Manage Connected Accounts** link, no `/connected-accounts` page

**Requirements:**
- `socialProviders.cloudflare.enabled` in the auth config (Cloudflare rides
  the genericOAuth plugin, not a Better Auth built-in) and the
  `AUTH_CLOUDFLARE_CLIENT_ID` / `AUTH_CLOUDFLARE_CLIENT_SECRET` secrets

## Account Management Features

### File Uploads

```bash
VITE_ENABLE_FILE_UPLOADS=false  # default: false
```

**When enabled:**
- Avatar/profile picture upload
- Image cropping and editing
- File upload to R2/S3

**When disabled:**
- No file upload functionality
- Users can only use default avatars

**Requirements:**
- R2 bucket or S3 configured
- Upload endpoints in your API

### Subscriptions

```bash
VITE_ENABLE_SUBSCRIPTIONS=false  # default: false
```

**When enabled:**
- Subscription card on the organization page
- Subscription management in the admin dashboard

**When disabled:**
- No billing surface in the Account UI

### Realtime

```bash
VITE_ENABLE_REALTIME=false  # default: false
```

**When enabled:**
- The Account SPA opens a realtime websocket connection and reacts to server broadcasts (including the best-effort `auth:token-invalidated` event described in [JWT Optimization](/platform/auth/jwt-optimization))

**When disabled:**
- No websocket connection

## Organization Features

### Organizations (Multi-Tenancy)

```bash
VITE_ENABLE_ORGANIZATIONS=false  # default: false
```

**When enabled:**
- `/dashboard` lists organizations and pending invitations
- Organization management pages (`/$slug`)
- Member invitations and roles
- `/organizations/*` routes
- Sign-in still lands on `/profile`; **Manage Organizations** opens `/dashboard`

**When disabled:**
- Single-user mode only
- No organization features
- Simpler user experience

**Includes:**
- Organization creation and deletion
- Member management (owner, admin, member roles)
- Invitation system
- Organization settings

### Teams

```bash
VITE_ENABLE_ORG_TEAMS=false  # default: false
```

Sub-groups within an organization. Auto-set to `true` by the compiler when your Better Auth config has `organization({ teams: { enabled: true } })`. When `false`, the Teams tab is hidden and the auth client does not expose team APIs.

## Admin Features

### Admin Panel

```bash
VITE_ENABLE_ADMIN=true  # default: true
```

**When enabled:**
- `/admin` route accessible to admin users
- User management dashboard
- Subscription management
- Admin-only features:
  - Create users manually
  - Ban/unban users
  - Reset user passwords
  - View all sessions
  - Manage subscriptions

**When disabled:**
- No admin panel
- Admin must use database directly

**Requirements:**
- User must have admin role in database

## Feature Combinations

### Minimal Configuration (Password-Only)

```bash
VITE_ENABLE_PASSWORD=true
VITE_ENABLE_PASSKEY=false
VITE_ENABLE_EMAIL_OTP=false
VITE_ENABLE_ORGANIZATIONS=false
VITE_ENABLE_ADMIN=false
```

Simple email/password authentication for apps that do not need advanced auth methods. Only `VITE_ENABLE_ADMIN` differs from the built-in defaults here.

### Maximum Security

```bash
VITE_ENABLE_PASSKEY=true
VITE_ENABLE_EMAIL_OTP=true
VITE_ENABLE_PASSWORD=false
VITE_ENABLE_EMAIL_VERIFICATION=true
```

Passwordless authentication with email verification and deliverability checks.

### Multi-Tenant SaaS

```bash
VITE_ENABLE_PASSKEY=true
VITE_ENABLE_EMAIL_OTP=true
VITE_ENABLE_ORGANIZATIONS=true
VITE_ENABLE_ORG_TEAMS=true
VITE_ENABLE_ADMIN=true
```

Full-featured SaaS with organizations, teams, and admin panel.

### Invite-Only Platform

```bash
VITE_ENABLE_SIGNUP=false
VITE_ENABLE_PASSKEY=true
VITE_ENABLE_ORGANIZATIONS=true
VITE_ENABLE_ADMIN=true
```

No public signup — users must be created by admin or invited to organizations.

## Feature Detection

Check if a feature is enabled in your code:

```ts
import { isFeatureEnabled } from '@/config/app';

if (isFeatureEnabled('organizations')) {
  // Show organizations menu
}

if (isFeatureEnabled('passkey')) {
  // Offer passkey setup
}
```

`isFeatureEnabled()` accepts only the ten `appConfig.features` keys listed above. The three `appConfig.auth` flags are read from `appConfig.auth` directly:

```ts
import { appConfig } from '@/config/app';

if (appConfig.auth.enableSignup) {
  // Show the sign-up link
}
```

## Dynamic Feature Configuration

Override features at runtime:

```ts
import { setAppConfig } from '@/config/app';

setAppConfig({
  features: {
    organizations: false,  // Hide organization-management UI
    passkey: true,         // Enable passkey
  },
});
```

> **setAppConfig wins over the baked env**
>
> `setAppConfig()` merges its overrides into the already-resolved config, so it takes precedence over both the runtime blob and the baked `VITE_*` values. Use it for progressive feature rollouts or A/B testing. It cannot resurrect anything the build dropped: when `organizations`, `passkey`, or `admin` is off at compile time the compiler removes those route files before Vite runs, so re-enabling them at runtime shows a menu entry pointing at a route that is not in the bundle.


## Testing Features

For local development, create `.env.local`:

```bash title=".env.local"
# Test with all features enabled
VITE_ENABLE_SIGNUP=true
VITE_ENABLE_EMAIL_VERIFICATION=true
VITE_ENABLE_PASSKEY=true
VITE_ENABLE_EMAIL_OTP=true
VITE_ENABLE_PASSWORD=true
VITE_ENABLE_ORGANIZATIONS=true
VITE_ENABLE_ADMIN=true
VITE_ENABLE_FILE_UPLOADS=true
VITE_DISABLE_EMAIL_STATUS_CHECK=true  # Allow test emails
```

## Next Steps

- **[Environment Variables](/ui/account/environment-variables)** - Complete variable reference
- **[Customization](/ui/account/customization)** - Customize UI text and labels
- **[With Quickback](/ui/account/with-quickback)** - How the compiler builds Account UI into your Worker
