Environment variables
Every env var by group, where it lives (Convex deployment vs Vercel), and the dev-fallback principle that lets most integrations run unset.
This page lists variable names and purpose only - never values. Do not commit
real keys; per-feature runbooks (SETUP.md, DEPLOY.md, docs/billing-setup.md,
docs/email-setup.md, docs/domain-system-plan.md) own the full setup steps.
For the repo-wide placement matrix, provider acquisition steps, safe smoke
results, and cleanup findings, see the internal operator runbook
docs/env-and-secrets.md. The tracked
templates are split by destination: .env.local.example,
.env.convex.example, .env.vercel.example, .env.prod.example,
.env.status.example, .env.github.example, .env.desktop.example, and
.env.test.example.
Where vars live
Two homes:
- Vercel /
.env.local- frontend and build vars (anythingNEXT_PUBLIC_*, plus the Clerk client/server keys read by the Next.js app and middleware). - The Convex deployment - server-only secrets used inside Convex actions (AI, Stripe, Resend, the Vercel domains API). Set them with:
npx convex env set NAME value # dev
npx convex env set NAME value --prod # production
CLERK_JWT_ISSUER_DOMAINis the one var that must be set in both places - the Next.js app and the Convex deployment - because Convex validates theconvexJWT template independently. See Auth & permissions.
The dev-fallback principle
Most integrations run in dev / mock mode until their credentials are set, so the whole app (and CI) works locally without secrets:
- No
OPENROUTER_API_KEY→ AI is disabled; the deterministic generator and the rest of the product still work fully. - No
RESEND_API_KEY→ transactional email is silently skipped (invites fall back to copy-link). - No Stripe keys → billing and payments are gated off (dev simulate paths).
- No
VERCEL_API_TOKEN/VERCEL_PROJECT_ID→ the domain connect/buy flow returns deterministic "connected" results (dev fallback), testable without credentials. - No
NEXT_PUBLIC_SITES_DOMAIN→ platform subdomains are dormant; sites stay reachable at the/site/<slug>path.
Core (Convex client)
| Var | Where | Purpose |
|---|---|---|
CONVEX_DEPLOYMENT | auto (convex dev) | Selects the dev deployment |
NEXT_PUBLIC_CONVEX_URL | auto (convex dev / deploy) | Convex client URL |
NEXT_PUBLIC_CONVEX_SITE_URL | optional | Override; otherwise derived from NEXT_PUBLIC_CONVEX_URL (lib/site/convexSiteUrl.ts) |
NEXT_PUBLIC_APP_URL | Vercel / .env.local (+ Convex for domains) | The app's own base URL |
NEXT_PUBLIC_APP_HOST | optional | The app's own host so middleware skips custom-domain lookups for it |
CONVEX_DEPLOY_KEY | Vercel (production build only) | Lets the Vercel build run convex deploy |
Clerk (auth - required)
| Var | Where | Purpose |
|---|---|---|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY | Vercel / .env.local | Clerk client key |
CLERK_SECRET_KEY | Vercel / .env.local and the Convex deployment (same secret) | Clerk server key; Convex verifies and atomically consumes desktop handoff codes |
CLERK_JWT_ISSUER_DOMAIN | .env.local and the Convex deployment | Issuer of the convex JWT template |
AI - OpenRouter (optional; on the Convex deployment)
OPENROUTER_API_KEY powers all AI (generation/polish, the chat assistant,
vision, dictation). No model is configured by environment any more (owner
directive 2026-08-13): every slug and reasoning effort is hardcoded in
lib/ai/models.ts. Any OPENROUTER_MODEL_* / OPENROUTER_REASONING_* variable
still set on a deployment is inert and should be deleted - see
Setup.
| Var | Purpose |
|---|---|
OPENROUTER_API_KEY | The single key for all AI (server-side only) |
TAVILY_API_KEY | Enables the chat's read-only webSearch tool |
See the AI system.
Leave Agent/Fast unset for plan-aware routing. Use them only to pin one chat speed globally across all plans.
Email - Resend (optional; on the Convex deployment)
| Var | Purpose |
|---|---|
RESEND_API_KEY | Enables transactional email (invites, lead + booking notifications) |
RESEND_FROM | The "from" identity for sent mail |
No key = email is silently skipped (docs/email-setup.md).
Billing & payments - Stripe (optional; on the Convex deployment)
| Var | Purpose |
|---|---|
STRIPE_SECRET_KEY | Stripe API key (billing + Connect payments) |
STRIPE_BILLING_WEBHOOK_SECRET | Verifies /api/stripe/billing-webhook |
STRIPE_CONNECT_WEBHOOK_SECRET | Verifies /api/stripe/connect-webhook (online card pay) |
STRIPE_WEBHOOK_SECRET | Verifies /api/stripe/domain-webhook (domain purchase) |
PAYMENTS_ALLOW_DEV_SIMULATE | Dev-only: simulate a successful payment (never in prod) |
Price ids and the operator runbook live in docs/billing-setup.md.
Domains & hosting - Name.com + Vercel (optional)
| Var | Where | Purpose |
|---|---|---|
DOMAIN_PROVIDER | Convex | namecom for the intended registrar; otherwise the dev mock (vercel is legacy) |
VERCEL_API_TOKEN | Convex | Attach connected or purchased domains to the Vercel host |
VERCEL_PROJECT_ID | Convex | Target Vercel project for domain attach |
VERCEL_TEAM_ID | Convex | Vercel team scope |
NEXT_PUBLIC_SITES_DOMAIN | Vercel / .env.local | Subdomain zone for published sites (e.g. the platform domain) |
VERCEL_ENV | auto (Vercel) | Set to production only on the prod deployment; gates the landing lab |
DOMAIN_BUY_ENABLED | Convex | Final owner-controlled gate for real registrar purchases |
NAMECOM_ENV / NAMECOM_USERNAME / NAMECOM_TOKEN | Convex | Name.com sandbox/production registration and renewal credentials |
DOMAIN_CONNECT_CLOUDFLARE_ENABLED | Convex | Enables the Cloudflare Domain Connect adapter |
DOMAIN_CONNECT_PRIVATE_KEY_JWK / DOMAIN_CONNECT_STATE_SECRET | Convex | Domain Connect signing/encryption secrets |
CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID | Convex | Cloudflare Email Routing account access |
EMAIL_PROVIDER | Convex | cloudflare for the real forwarding adapter; unset uses the dev mock |
See Deployment and
docs/domain-system-plan.md. No domain secrets are ever stored in Convex tables.
Misc & native shells (optional)
| Var | Purpose |
|---|---|
NEXT_PUBLIC_SUPPORT_EMAIL | Public support address shown in the UI |
NEXT_PUBLIC_DESKTOP_DMG_URL | Download link shown on /download |
SAJT_DESKTOP_URL | Web app the Tauri desktop shell loads |
APPLE_TEAM_ID / IOS_BUNDLE_ID | iOS build-time identifiers |
CAP_SERVER_URL / CAP_LOGGING_BEHAVIOR | Capacitor iOS build-time configuration |
BLOB_READ_WRITE_TOKEN | Local desktop release upload to Vercel Blob |
Status, automation, and test-only variables
These do not belong in the browser bundle or in the Convex deployment unless the code path explicitly consumes them:
| Var | Where | Purpose |
|---|---|---|
STATUS_ADMIN_PASSWORD / STATUS_SESSION_SECRET | Railway status-worker | Password gate + HMAC session cookie; use two different random values |
STATUS_PUBLIC_URL / STATUS_DEFAULT_LANG | Railway status-worker | Public status URL and default language |
PROBE_* | Railway status-worker | Optional health-check targets; missing targets are skipped |
SENTRY_AUTH_TOKEN / SENTRY_ORG / SENTRY_PROJECT | GitHub Actions or build | Source-map upload and auto-repair polling |
OPENROUTER_AUTOFIX_API_KEY | GitHub Actions | Separate capped key for the repair workflow |
VERCEL_AUTOFIX_TOKEN / VERCEL_AUTOFIX_PROJECT_ID / VERCEL_AUTOFIX_TEAM_ID | GitHub Actions | Auto-repair deployment polling |
E2E_BASE_URL / E2E_SIGNIN_TOKEN / E2E_TEST_EMAIL | Local/CI test runner | Authenticated browser tests |
The platform-injected values NODE_ENV, NEXT_RUNTIME, VERCEL,
VERCEL_ENV, CI, PATH, and PORT are not hand-configured in the example
files.
Didn't find the answer, or is something wrong here? Tell us.
Last updated on
Hand off to the client
Give the client their own account without giving them everything - invite as an editor, decide what they may use, and understand the one move that voids every limit you set.
Deployment
How SnabbSajt deploys to Vercel + Convex, the post-deploy auth check, going live on gated integrations, and the dev-only routes that 404 in production.