# Configuration

> The environment variables the server reads: the required three, then sync, batching, storage, email, billing and scale.

Source: https://baalda.com/docs/configuration
Site: Baalda, https://baalda.com

Everything is read from the environment at startup. Only three variables are required. The rest have defaults that suit a single-instance deployment.

## Required

| Variable | Default | What it is |
|---|---|---|
| `DATABASE_URL` | dev-only local value | Postgres connection string. |
| `JWT_SECRET` | dev-only insecure default | Signs sessions and sync tokens. Generate one with `openssl rand -base64 32`. Rotating it invalidates every session and sync token. |
| `BETTER_AUTH_URL` | `http://localhost:3010` | The public base URL used in auth links such as verification and invitations. It must match the URL clients actually use. |

## Serving and sync

| Variable | Default | What it is |
|---|---|---|
| `PORT` | 3010 | HTTP API port, which serves both WebSocket paths as well. |
| `HOCUSPOCUS_PORT` | 3011 | Legacy dedicated editing-socket port. |
| `VAULT_SYNC_PATH` | `/vault-sync` | Path of the background vault channel on `PORT`. |
| `CORS_ORIGINS` | unset | Comma-separated origins allowed to call the API. |
| `SYNC_TOKEN_TTL_SECONDS` | 600 | Lifetime of a per-document sync token. |
| `COMPACTION_THRESHOLD` | 50 | Pending CRDT updates before the server compacts them into a snapshot. |
| `PG_POOL_MAX` | 30 | Connections this instance pools. Keep the total across instances under the database's own limit, and never below `BACKFILL_CONCURRENCY`. |

## Batching and first sync

These bound how much one request may carry. The desktop app chunks to them, so raising them is rarely necessary and lowering them is a safe way to protect a small instance.

| Variable | Default | What it is |
|---|---|---|
| `BATCH_MAX_NOTES` | 200 | Notes per batch request. Past it the request is refused as too large. |
| `BATCH_MAX_FOLDERS` | 500 | Folders per batch. Higher because a folder row is cheaper. |
| `BATCH_MAX_FILES` | 200 | Files per batch. |
| `BATCH_MAX_DOCS` | 100 | CRDT content items per batch. |
| `BATCH_MAX_DECODED_BYTES` | 4 MiB | Total decoded update bytes one content batch may carry. This is the heap bound behind the request body limit. |
| `BOOTSTRAP_MAX_PAGE_BYTES` | 4 MiB | Byte budget for one bootstrap page. A single document larger than a page ships alone rather than being refused. |
| `BOOTSTRAP_MAX_PAGE_DOCS` | 256 | Document budget for one bootstrap page. |
| `BOOTSTRAP_CONCURRENCY` | 4 | Bootstrap pages built at once per instance. Past it a request answers 503 with a Retry-After. Raise it only alongside the container's memory. |
| `BOOTSTRAP_TTL_HOURS` | 24 | How long a bootstrap session stays valid before the client must start again. |
| `BACKFILL_CONCURRENCY` | 6 | Documents streamed at once to a freshly connected vault subscriber. |

## Attachments

Summarised here, with the full behaviour on the attachment storage page.

| Variable | Default | What it is |
|---|---|---|
| `BLOB_STORAGE` | `postgres` | `postgres` or `s3`. An unrecognised value, or `s3` with an incomplete bucket configuration, is a fatal startup error. |
| `MAX_BLOB_BYTES` | 25 MB | Ceiling for one attachment on Postgres. |
| `MAX_BLOB_BYTES_DIRECT` | 500 MB | Ceiling for one attachment on S3. |
| `MAX_INFLIGHT_UPLOAD_BYTES` | 2x `MAX_BLOB_BYTES` | Upload bytes admitted at once. Beyond it uploads queue, then shed with a 503. |
| `BLOB_MIME_ENFORCE` | `reject` | `reject` answers 415 for an unknown content type. `warn` logs and stores it. |
| `BLOB_PENDING_TTL_MINUTES` | 60 | How long an abandoned upload holds its dedupe slot. |
| `BLOB_GC_ENABLED` | `false` | Whether unreferenced attachments are collected. |

## Email

Optional, and off unless you configure it. See the email page for what each transport needs.

| Variable | Default | What it is |
|---|---|---|
| `EMAIL_FROM` | unset | Sender address. With this and one transport, password reset, sign-up verification and invitation emails switch on. |
| `SMTP_URL` | unset | Any SMTP server, with STARTTLS or TLS. |
| `RESEND_API_KEY` | unset | The HTTPS alternative to SMTP. |
| `EMAIL_TRANSPORT` | inferred | Force a transport instead of inferring it. `log` and `memory` exist for development and are refused in production. |

## Billing

Leave every billing variable unset and billing is fully disabled: no upgrade interface in the clients, no free-tier limits, and every self-hosted vault unlimited. Setting them turns on subscriptions through Polar.

| Variable | Default | What it is |
|---|---|---|
| `POLAR_ACCESS_TOKEN` | unset | Turns billing on. Unset means disabled. |
| `POLAR_WEBHOOK_SECRET` | unset | Signing secret of the webhook endpoint pointed at `/api/billing/webhook`. |
| `BILLING_MODEL` | `vault` | `vault` for per-vault plans with the free caps, or `team` for one account sold per seat. |
| `TEAM_PRICE_MONTHLY_CENTS` / `TEAM_PRICE_YEARLY_CENTS` | 1000 / 11000 | Per-seat prices shown in the app. Polar's product price is what is actually charged. |
| `TEAM_MIN_SEATS` | 3 | Smallest seat count a checkout accepts. |
| `FREE_MAX_VAULTS` / `FREE_MAX_MEMBERS` / `FREE_MAX_STORAGE_MB` | 2 / 3 / 1024 | Per-vault model only. Free-tier caps, enforced only when billing is on. |
| `ABUSE_MAX_NOTES` / `ABUSE_MAX_STORAGE_MB` | 100000 / 10240 | Team model only. Per-vault ceilings for free accounts. Never applied with billing off. |

## Other

| Variable | Default | What it is |
|---|---|---|
| `REDIS_URL` | unset | Unset means a single instance with in-memory fanout. Set it to run several instances. See scaling. |
| `BAALDA_DEPLOYMENT` | `cloud` | `self-hosted` lets people use their own model keys for server-side AI without any billing setup. `cloud` requires a paid plan for it. |
| `DEEP_LINK_SCHEME` | `baalda` | The app scheme the server's pages bounce into. Set `baalda-staging` on a staging server so links open the right build. |
| `OPENAI_API_KEY` | unset | Optional embeddings for semantic search. The server works fully offline without it. |

> The deployment document in the repository carries the complete table, including the garbage-collection schedule and every value not listed here.
