# Environment Variables

Configure the Account UI by setting environment variables in your `.env` file or deployment platform.

> **Runtime config takes precedence**
>
> Every variable on this page can also be delivered at **serve time** via the worker-injected `window.__QUICKBACK_RUNTIME` blob. When a Quickback-generated worker serves the Account UI (unified `/account`, custom domain, or dev hostnames), it injects the project's values into the HTML shell automatically. Resolution precedence for every per-project field is:
>
> 1. `window.__QUICKBACK_RUNTIME.<field>` (injected by the serving worker)
> 2. Baked `VITE_*` env (this page — set at build time)
> 3. Built-in default
>
> This means a single generic build of the Account UI (no `.env`) behaves identically to a per-project baked build when served by the worker. Baked builds keep working unchanged — the blob simply wins when both are present.


## Quick Setup — Single Variable

For the common "unified deployment" where API, Account, and CMS all live on the same Quickback origin, you only need **one** variable:

```bash
# Points at the Quickback Worker origin
VITE_QUICKBACK_URL=https://secure.example.com
```

When `VITE_QUICKBACK_URL` is set, it's used as a fallback for the individual URL vars:

| Individual var | Used first | Falls back to |
|---|---|---|
| `VITE_QUICKBACK_API_URL` | if set | `${VITE_QUICKBACK_URL}` |
| `VITE_QUICKBACK_ACCOUNT_URL` | if set | `${VITE_QUICKBACK_URL}/account` |
| `VITE_QUICKBACK_CMS_URL` | if set | `${VITE_QUICKBACK_URL}/cms` |

> **VITE_QUICKBACK_APP_URL is independent**
>
> `VITE_QUICKBACK_APP_URL` points at the tenant's own frontend (where users are redirected after login). It's **not** derived from `VITE_QUICKBACK_URL` — set it separately if needed.


## Required Variables

At minimum, you need one of:

- **`VITE_QUICKBACK_URL`** — catch-all for the Quickback origin (recommended), **or**
- **`VITE_QUICKBACK_API_URL`** — explicit API URL (use this if your API lives on a different subdomain from Account/CMS)

### Split-subdomain setup

If your API, Account, and CMS live on separate custom subdomains, set them explicitly:

```bash
# Quickback API URL
VITE_QUICKBACK_API_URL=https://api.example.com

# Account UI URL (where the Account UI is deployed)
VITE_ACCOUNT_APP_URL=https://account.example.com

# Optional: override Account/CMS URLs for cross-origin links
VITE_QUICKBACK_ACCOUNT_URL=https://auth.example.com
VITE_QUICKBACK_CMS_URL=https://cms.example.com
```

> **The fallback is same-origin, not an error**
>
> If neither `VITE_QUICKBACK_URL` nor `VITE_QUICKBACK_API_URL` resolves (and the worker injected no `apiUrl`), the API base falls back to `window.location.origin`. Nothing throws — the SPA just talks to whatever host served it. That is exactly right on a unified deployment and silently wrong when the API lives elsewhere, so set one of them explicitly for split-subdomain setups.


## App Identity

Configure your application's basic information:

```bash
# App name (displayed in UI)
VITE_APP_NAME=My Application

# Tagline (shown on landing pages)
VITE_APP_TAGLINE=Build faster, ship sooner

# Full description
VITE_APP_DESCRIPTION=A complete platform for building modern web applications

# Company information
VITE_COMPANY_NAME=My Company Inc.
VITE_COMPANY_ADDRESS=123 Main St, Suite 100, San Francisco, CA 94105
```

## URLs

### Main Application

```bash
# Your main application URL
# Users will be redirected here after authentication
VITE_QUICKBACK_APP_URL=https://app.example.com

# Organization/Tenant URL pattern (optional)
# Use {slug} as placeholder for organization identifier
# Examples:
#   /organizations/{slug}           -> app.example.com/organizations/acme
#   /workspace/{slug}               -> app.example.com/workspace/acme
#   https://{slug}.example.com      -> acme.example.com
VITE_TENANT_URL_PATTERN=/organizations/{slug}
```

### Support and Legal

```bash
# Support/help URL
VITE_SUPPORT_URL=https://support.example.com

# Privacy policy URL
VITE_PRIVACY_URL=https://example.com/privacy

# Terms of service URL
VITE_TERMS_URL=https://example.com/terms
```

## Email Configuration

```bash
# From address for all emails
VITE_EMAIL_FROM=noreply@example.com

# Reply-to address
VITE_EMAIL_REPLY_TO=support@example.com

# Support email address
VITE_SUPPORT_EMAIL=support@example.com

# AWS Region for SES (if using AWS SES)
VITE_EMAIL_REGION=us-east-1
```

## Branding

```bash
# Primary theme color (hex code)
VITE_THEME_COLOR=#1e293b

# SEO keywords (comma-separated)
VITE_SEO_KEYWORDS=saas,authentication,account management
```

## Feature Flags

Enable or disable features using boolean environment variables. **Every flag carries the `VITE_` prefix** — Vite only exposes `VITE_*` vars to client code, so an unprefixed `ENABLE_PASSKEY=true` is read by nothing.

### `appConfig.features`

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

| 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, Google OAuth, subscriptions, and realtime all default to `false`. Admin defaults to `true`.
>
> These are the SPA's **built-in** defaults. In a compiled 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 — notably `organizations` resolves to `true` unless you opt out. See [Feature Flags](/ui/account/features) for the derivations that differ.


```bash
# Authentication
VITE_ENABLE_SIGNUP=true                 # default: true
VITE_ENABLE_EMAIL_VERIFICATION=true     # default: true
VITE_DISABLE_EMAIL_STATUS_CHECK=false   # default: false — inverted sense
VITE_ENABLE_PASSWORD=true               # default: true
VITE_ENABLE_PASSKEY=false               # default: false
VITE_ENABLE_EMAIL_OTP=false             # default: false
VITE_ENABLE_GOOGLE_OAUTH=false          # default: false

# Account management
VITE_ENABLE_FILE_UPLOADS=false          # default: false
VITE_ENABLE_SUBSCRIPTIONS=false         # default: false
VITE_ENABLE_REALTIME=false              # default: false

# Organizations
VITE_ENABLE_ORGANIZATIONS=false         # default: false
VITE_ENABLE_ORG_TEAMS=false             # default: false
# Auto-set to true when the BA config has organization({ teams: { enabled: true } }).
# When false the Teams tab is hidden and the auth client doesn't expose team APIs.

# Admin
VITE_ENABLE_ADMIN=true                  # default: true
```

Per-flag behaviour is documented under [Feature Flags](/ui/account/features).

## Complete Example

Here's a complete `.env` file with all common configuration:

```bash title=".env"
# ===== REQUIRED =====
VITE_QUICKBACK_API_URL=https://api.example.com
VITE_ACCOUNT_APP_URL=https://account.example.com

# ===== APP IDENTITY =====
VITE_APP_NAME=Acme SaaS
VITE_APP_TAGLINE=Build faster, ship sooner
VITE_APP_DESCRIPTION=The complete platform for modern web applications
VITE_COMPANY_NAME=Acme Corporation
VITE_COMPANY_ADDRESS=123 Main St, Suite 100, San Francisco, CA 94105

# ===== URLS =====
VITE_QUICKBACK_APP_URL=https://app.acme.com
VITE_TENANT_URL_PATTERN=/organizations/{slug}
VITE_SUPPORT_URL=https://help.acme.com
VITE_PRIVACY_URL=https://acme.com/privacy
VITE_TERMS_URL=https://acme.com/terms

# ===== EMAIL =====
VITE_EMAIL_FROM=noreply@acme.com
VITE_EMAIL_REPLY_TO=support@acme.com
VITE_SUPPORT_EMAIL=support@acme.com
VITE_EMAIL_REGION=us-east-1

# ===== BRANDING =====
VITE_THEME_COLOR=#3b82f6

# ===== SEO =====
VITE_SEO_KEYWORDS=saas,project management,team collaboration

# ===== FEATURES =====
VITE_ENABLE_SIGNUP=true
VITE_ENABLE_EMAIL_VERIFICATION=true
VITE_ENABLE_PASSKEY=true
VITE_ENABLE_EMAIL_OTP=true
VITE_ENABLE_PASSWORD=false
VITE_ENABLE_ORGANIZATIONS=true
VITE_ENABLE_ORG_TEAMS=true
VITE_ENABLE_ADMIN=true
VITE_ENABLE_FILE_UPLOADS=true
VITE_VITE_DISABLE_EMAIL_STATUS_CHECK=false
```

## Environment-Specific Configuration

### Development

```bash title=".env.development"
VITE_QUICKBACK_API_URL=http://localhost:8787
VITE_ACCOUNT_APP_URL=http://localhost:5173
VITE_QUICKBACK_APP_URL=http://localhost:3000
VITE_DISABLE_EMAIL_STATUS_CHECK=true
```

### Staging

```bash title=".env.staging"
VITE_QUICKBACK_API_URL=https://api-staging.example.com
VITE_ACCOUNT_APP_URL=https://account-staging.example.com
VITE_QUICKBACK_APP_URL=https://app-staging.example.com
```

### Production

```bash title=".env.production"
VITE_QUICKBACK_API_URL=https://api.example.com
VITE_ACCOUNT_APP_URL=https://account.example.com
VITE_QUICKBACK_APP_URL=https://app.example.com
VITE_DISABLE_EMAIL_STATUS_CHECK=false
```

## Deployment

The Account UI has no standalone deploy path — the compiler ships a generic prebuilt bundle (built once per compiler release) into your Worker. In a compiled project you normally set none of the variables on this page by hand:

1. Declare the values in `quickback.config.ts` under `account` (name, branding, URLs, `auth` feature flags).
2. `quickback compile` stages the prebuilt Account assets and bakes the values into the generated worker.
3. The worker injects them into every served Account shell as `window.__QUICKBACK_RUNTIME`, which the SPA prefers over baked `VITE_*` values. This runtime blob is the only per-project configuration channel — no per-project Vite build runs.

```bash
quickback compile
npx wrangler deploy
```

See [With Quickback](/ui/account/with-quickback) for the full pipeline.

> **`VITE_*` vars are not worker vars**
>
> Do not put `VITE_*` entries in `wrangler.toml` `[vars]` or `.dev.vars`. Those are Worker runtime bindings; Vite reads its env at **build** time and the runtime blob is baked into the worker source at compile time. Neither one consults `[vars]`.


The `.env` files documented above apply when you run the Account SPA outside a Quickback worker — local `vite dev` against a remote API, or hosting the built assets yourself. In that case the values are baked at build time and changing one requires a rebuild.

## Runtime Configuration

Some values can be overridden at runtime using the config API:

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

setAppConfig({
  name: 'My Custom App',
  branding: {
    primaryColor: '#ff6600',
    logoUrl: '/custom-logo.png',
  },
  urls: {
    base: 'https://account.custom.com',
    app: 'https://app.custom.com',
  },
});
```

> **Limitations**
>
> Runtime configuration only works for values consumed by the React application. Environment variables used during the Vite build process cannot be changed at runtime.


## Resolution behaviour

Config resolution is lenient — there is no startup validation pass and no throw for a missing URL:

- **No API URL resolved** — falls back to `window.location.origin`
- **No `VITE_QUICKBACK_ACCOUNT_URL` / `VITE_QUICKBACK_CMS_URL`** — derived from `VITE_QUICKBACK_URL` as `${base}/account` and `${base}/cms`; `null` if that is unset too
- **Missing optional fields** — use the built-in defaults listed above

The two calls that *do* throw are `getDataApiUrl()` and `getStorageApiUrl()`, when `authRoute` is not `"quickback"` and `routes.api.data` / `routes.api.storage` were never configured.

## Next Steps

- **[Feature Flags](/ui/account/features)** - Detailed feature configuration
- **[Customization](/ui/account/customization)** - Customize labels and messages
- **[With Quickback](/ui/account/with-quickback)** - How the compiler builds and bundles Account UI
