Configuration
canvas-drop is configured entirely by environment variables, validated at boot. A
single config module is the only reader of process.env; everything else takes
typed config. Misconfiguration fails loudly at startup, not at request time.
This page is the env-var reference. The defaults below are the schema defaults
(authoritative); .env.example in the repo mirrors them with inline comments.
There is no telemetry and no phone-home — nothing is reported anywhere.
How config gets loaded
Local dev:
pnpm devreads a root.envfile once (node --env-file-if-exists=.env). Copy the template and start:cp .env.example .env && pnpm devZero-config defaults give you a logged-in instance on
http://localhost:3000: path URL mode + sqlite + local storage + dev auth.Production: the
.envfile is not read. Set variables in your process environment — a systemdEnvironmentFile, container env, or secrets manager.
The four pluggable interfaces
Each interface is selected by one switch variable. Swapping a driver is a config change, never a code change.
| Interface | Switch | Options (default first) | Driver-specific vars |
|---|---|---|---|
| Database | CANVAS_DROP_DB |
sqlite / postgres |
sqlite: CANVAS_DROP_SQLITE_PATH; postgres: CANVAS_DROP_DATABASE_URL |
| Storage | CANVAS_DROP_STORAGE |
local / s3 |
local: CANVAS_DROP_STORAGE_PATH; s3: CANVAS_DROP_S3_* |
| URL mode | CANVAS_DROP_URL_MODE |
path / subdomain |
subdomain requires a non-localhost CANVAS_DROP_BASE_URL |
| Auth | CANVAS_DROP_AUTH_MODE |
dev / proxy / oidc |
proxy: JWT or trusted-header vars; oidc: CANVAS_DROP_OIDC_*; dev: CANVAS_DROP_DEV_USER_* |
Core
| Variable | Default | Notes |
|---|---|---|
NODE_ENV |
development |
development | production | test. |
CANVAS_DROP_URL_MODE |
path |
path serves …/c/{slug}/*; subdomain serves {slug}.{baseHost}. |
CANVAS_DROP_BASE_URL |
http://localhost:3000 |
Public base URL. In subdomain mode must be non-localhost or boot fails. |
CANVAS_DROP_API_BASE_URL |
(= CANVAS_DROP_BASE_URL) |
Where the Deploy API (/v1/canvases/*) is reachable. Set only when the API is fronted on a different host than canvases — e.g. subdomain mode where canvases are {slug}.canvases.example.com but the API is routed at https://api.canvases.example.com. The MCP tools advertise curl endpoints built from this so agents don't probe for the host. |
CANVAS_DROP_PORT |
3000 |
Listen port. |
CANVAS_DROP_SESSION_SECRET |
(dev fallback only) | Required, ≥32 chars, outside dev and always in production. Signs session cookies (oidc/dev). |
CANVAS_DROP_ADMIN_EMAILS |
(empty) | CSV; lowercased. Grants the admin surface. |
CANVAS_DROP_REALTIME |
on |
on | off. Toggles the realtime primitive. |
CANVAS_DROP_MCP |
on |
on | off. When off, the /mcp control-plane routes are not mounted. |
CANVAS_DROP_ALLOW_MULTI_USER_PATH_MODE |
false |
Must be true to boot path mode with proxy/oidc auth (path mode has reduced browser isolation). |
CANVAS_DROP_DASHBOARD_DIST |
(resolved) | Override the built dashboard SPA path; defaults to a location resolved from the server module. |
Auth
CANVAS_DROP_AUTH_MODE selects how identity is established. Identity always comes
from the server-side auth strategy, never from anything the client sends.
| Variable | Default | Notes |
|---|---|---|
CANVAS_DROP_AUTH_MODE |
dev |
dev | proxy | oidc. dev is rejected when NODE_ENV=production. |
CANVAS_DROP_ALLOWED_EMAIL_DOMAINS |
(empty) | CSV, lowercased. Required (≥1) in proxy/oidc. |
Individual email allowlist. Beyond the domain list above, an admin can allow specific outside emails to sign in (e.g. a contractor or a test account) under Admin → Users → Allowed sign-in emails. It's an additive, DB-managed layer: the env domain list is unchanged, and an email passes if its domain is allowed or it's on this list. Removing an entry revokes that email's access on its next sign-in.
dev mode
Auto-logs-in a fixed local user; zero setup, localhost only.
| Variable | Default |
|---|---|
CANVAS_DROP_DEV_USER_EMAIL |
dev@example.com |
CANVAS_DROP_DEV_USER_NAME |
Dev User |
oidc mode
The app owns login via Authorization-Code + PKCE.
| Variable | Default | Notes |
|---|---|---|
CANVAS_DROP_OIDC_ISSUER |
(unset) | Required. Provider issuer URL. |
CANVAS_DROP_OIDC_CLIENT_ID |
(unset) | Required. |
CANVAS_DROP_OIDC_CLIENT_SECRET |
(unset) | Required. |
proxy mode
Front the app with an identity-aware proxy. Exactly one trust path is active — they do not compose. Configure either the JWT/JWKS path (preferred, cryptographic) or the trusted-header path. Boot fails if neither is set.
| Variable | Default | Notes |
|---|---|---|
CANVAS_DROP_AUTH_PROXY_EMAIL_HEADER |
X-Auth-Request-Email |
Identity header (trusted-header path). |
CANVAS_DROP_AUTH_PROXY_NAME_HEADER |
X-Auth-Request-Preferred-Username |
Display-name header. |
CANVAS_DROP_AUTH_PROXY_JWT_HEADER |
Cf-Access-Jwt-Assertion |
Header carrying the proxy's signed JWT. |
CANVAS_DROP_AUTH_PROXY_JWT_JWKS_URL |
(unset) | Enables the JWT path. When set, identity headers are never honored. |
CANVAS_DROP_AUTH_PROXY_JWT_ISSUER |
(unset) | Required when JWKS is set. |
CANVAS_DROP_AUTH_PROXY_JWT_AUDIENCE |
(unset) | Required when JWKS is set. |
CANVAS_DROP_TRUSTED_PROXY_IPS |
(empty) | CSV of IPv4 addresses or CIDRs. The only peers whose identity headers are trusted. Gates on the socket peer IP (never X-Forwarded-For). Each entry is validated at boot: /0, malformed IPv4, and IPv6 are rejected. |
In proxy mode you must set either CANVAS_DROP_AUTH_PROXY_JWT_JWKS_URL (with
its issuer and audience) or CANVAS_DROP_TRUSTED_PROXY_IPS.
Database
| Variable | Default | Notes |
|---|---|---|
CANVAS_DROP_DB |
sqlite |
sqlite | postgres. |
CANVAS_DROP_SQLITE_PATH |
./data/canvasdrop.db |
sqlite file path. |
CANVAS_DROP_DATABASE_URL |
(unset) | Required when CANVAS_DROP_DB=postgres. |
Storage
| Variable | Default | Notes |
|---|---|---|
CANVAS_DROP_STORAGE |
local |
local | s3. |
CANVAS_DROP_STORAGE_PATH |
./data/storage |
local storage root. |
CANVAS_DROP_S3_ENDPOINT |
(unset) | Custom endpoint for MinIO / R2. |
CANVAS_DROP_S3_BUCKET |
(unset) | Required when CANVAS_DROP_STORAGE=s3. |
CANVAS_DROP_S3_REGION |
(unset) | Required when s3. |
CANVAS_DROP_S3_ACCESS_KEY |
(unset) | Required when s3. |
CANVAS_DROP_S3_SECRET_KEY |
(unset) | Required when s3. |
CANVAS_DROP_S3_FORCE_PATH_STYLE |
true |
Keep true for MinIO. |
Rate limiting
Per-minute request budgets. Each limit is a positive integer ≥ 1 (boot fails on
0 or negative).
| Variable | Default | Notes |
|---|---|---|
CANVAS_DROP_RATELIMIT_ENABLED |
true |
Master toggle. |
CANVAS_DROP_RATELIMIT_CANVAS_API_PER_MIN |
120 |
|
CANVAS_DROP_RATELIMIT_AI_PER_MIN |
10 |
|
CANVAS_DROP_RATELIMIT_DEPLOY_PER_MIN |
10 |
Keyed per canvas. |
CANVAS_DROP_RATELIMIT_MANAGEMENT_PER_MIN |
120 |
|
CANVAS_DROP_RATELIMIT_LOGIN_PER_MIN |
10 |
Keyed on resolved client IP. |
CANVAS_DROP_RATELIMIT_PASSWORD_GATE_PER_MIN |
5 |
AI (optional)
The AI primitive is off until CANVAS_DROP_AI_API_KEY is set. The key is
server-side only and is never exposed to the browser.
| Variable | Default | Notes |
|---|---|---|
CANVAS_DROP_AI_PROVIDER |
anthropic |
|
CANVAS_DROP_AI_API_KEY |
(unset) | Unset/blank disables AI. |
CANVAS_DROP_AI_BASE_URL |
(unset) | Override the provider base URL. |
CANVAS_DROP_AI_MODELS |
claude-haiku-4-5,claude-sonnet-4-6,claude-opus-4-8 |
CSV allowlist. |
CANVAS_DROP_AI_USER_DAILY_USD |
5 |
Per-user daily spend cap. |
CANVAS_DROP_AI_CANVAS_MONTHLY_USD |
50 |
Per-canvas monthly spend cap. |
Email (guest invites)
Sends the magic-link sign-in emails for email-invited guests (the
specific_people access rung). It is a driver-behind-interface like the database
and storage, so adding a future provider is a config change. Guest invites and
public links are app-gated-mode features (oidc/dev); in proxy mode the
upstream IAP owns the boundary and invites are refused. Provider secrets are
server-side only and never logged.
| Variable | Default | Notes |
|---|---|---|
CANVAS_DROP_EMAIL_DRIVER |
log |
log (writes the link to the server log — zero-setup dev) | smtp | mailgun | noop (disables invites). |
CANVAS_DROP_EMAIL_FROM |
no-reply@<mailgun domain> or no-reply@localhost |
Sender address. |
CANVAS_DROP_SMTP_HOST |
(unset) | SMTP server host (driver smtp). |
CANVAS_DROP_SMTP_PORT |
587 |
587 = STARTTLS, 465 = implicit TLS. |
CANVAS_DROP_SMTP_USER / CANVAS_DROP_SMTP_PASS |
(unset) | Omit both for an IP-allowlisted relay. |
CANVAS_DROP_SMTP_SECURE |
false |
true for implicit TLS (port 465). |
CANVAS_DROP_MAILGUN_API_KEY |
(unset) | Mailgun HTTP API key (driver mailgun). |
CANVAS_DROP_MAILGUN_DOMAIN |
(unset) | e.g. mg.example.com. |
CANVAS_DROP_MAILGUN_BASE_URL |
https://api.mailgun.net |
Use https://api.eu.mailgun.net for EU. |
Logging & error tracking
Structured logs go to stdout via pino — no app-side files, rotation, or shipping.
| Variable | Default | Notes |
|---|---|---|
LOG_LEVEL |
info |
fatal | error | warn | info | debug | trace. |
LOG_FORMAT |
pretty in dev, json in production |
json | pretty. |
CANVAS_DROP_SENTRY_DSN |
(unset) | Error tracking is off unless this is set. |
All examples use placeholder values. Never commit real secrets — set them in your deployment environment (systemd, container env, secrets manager).