Environment variables
Every variable in apps/web/.env.example and apps/admin/.env.example, what it does and where its value comes from.
This kit has two env files, one per deployable app: apps/web/.env (from apps/web/.env.example) and apps/admin/.env (from apps/admin/.env.example). Each Express server loads its own file with dotenv at startup, and src/.server/env.ts in each app validates the core values with Zod. Optional integrations are read lazily by src/.server/integrations.ts and stay disabled when their variables are empty.
The two example files are identical except for the admin app's PORT, APP_URL and the extra WEB_APP_URL. pnpm supabase:sync-env writes the three Supabase values into both files.
Never commit either .env file. The Vercel packaging scripts refuse to bundle any .env* file, and .gitignore excludes everything except the examples.
Public versus server-only
Nothing in these files is inlined into the browser bundle by Vite. The servers pass SUPABASE_URL and SUPABASE_ANON_KEY to the browser through loader data for realtime subscriptions, so treat those two as public. Everything else is server-only. SUPABASE_SERVICE_ROLE_KEY, Stripe secrets and API_KEY_PEPPER must never appear in loader data, client code or logs.
Server and Supabase
| Variable | Required | Purpose | Where to get it |
|---|---|---|---|
NODE_ENV | Optional | development, test or production. Defaults to development. In production the server serves build/client instead of starting Vite. | Set production on your host. |
HOST | Optional | Interface Express binds to. Defaults to 0.0.0.0. | Your platform. Ignored on Vercel, which does not call listen. |
PORT | Optional | Port Express listens on. Defaults to 3000 in code; the admin example sets 3002. Playwright reads the web value too. | Your platform. Pick a different port per worktree when running several servers. |
APP_URL | Required in production | Public HTTP(S) origin of this app. Must have no path, query, hash or credentials. Drives the trusted form-action origin, auth email links and the MCP issuer and resource URLs. | Web: http://localhost:3000 locally, your product domain in deployment. Admin: http://localhost:3002 locally, your admin domain in deployment. |
WEB_APP_URL | Admin only, required in production | Origin of the product web app. The admin app uses it to build support sign-in links that land on the web app's /auth/confirm. Same origin rules as APP_URL. | http://localhost:3000 locally; the web deployment's APP_URL in production. Not present in apps/web/.env.example. |
SUPABASE_URL | Required | Supabase project API URL. Must be set together with SUPABASE_ANON_KEY. Public. | env.API_URL from supabase status --output-format json (the example file's placeholder says so), or the project's API settings. |
SUPABASE_ANON_KEY | Required | Anonymous key for the request-scoped session client. RLS applies. Public. | env.ANON_KEY from the status output, or the project's API settings. |
SUPABASE_SERVICE_ROLE_KEY | Required in production | Service-role key for the server-only client used after authorization checks, billing webhooks, admin pages and the MCP store. Bypasses RLS. | env.SERVICE_ROLE_KEY from the status output, or the project's API settings. |
SUPABASE_SERVICE_ROLE_KEY requires SUPABASE_URL. In production, the web app's /health/ready returns 503 until APP_URL, SUPABASE_URL, SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY are all present; the admin app's /health/ready additionally requires WEB_APP_URL.
Optional billing (Stripe)
| Variable | Required | Purpose | Where to get it |
|---|---|---|---|
STRIPE_SECRET_KEY | Optional | Enables checkout, customer portal, subscriptions and invoices. Billing shows a disabled state when empty. | Stripe dashboard, API keys. |
STRIPE_WEBHOOK_SECRET | Optional | Verifies events posted to /api/stripe/webhook on the web app. | Stripe dashboard, the webhook endpoint's signing secret. |
STRIPE_API_URL | Optional | Overrides the Stripe API base. Defaults to https://api.stripe.com. | Only change it for a proxy or test double. |
Set the Stripe values on the admin app too if you use its billing administration pages.
Optional transactional email (Resend)
| Variable | Required | Purpose | Where to get it |
|---|---|---|---|
RESEND_API_KEY | Optional | Lets sendTransactionalEmail in src/.server/mail.ts deliver app emails. Both this and EMAIL_FROM must be set. | Resend dashboard. |
RESEND_API_URL | Optional | Overrides the Resend API base. Defaults to https://api.resend.com. | Only change it for a proxy or test double. |
EMAIL_FROM | Optional | Sender address for app emails. | An address on a domain you verified in Resend. |
EMAIL_REPLY_TO | Optional | Reply-to address added to app emails. | Your support address. |
These variables do not configure Supabase Auth emails (confirmation, magic link, recovery). Those are sent by Supabase using the project's Auth SMTP settings; locally they land in Mailpit. See emails with Resend.
Optional developer API
| Variable | Required | Purpose | Where to get it |
|---|---|---|---|
API_KEY_PEPPER | Optional | Enables first-party API keys and /api/v1/me. Ignored unless it has at least 32 characters. | Generate a random string of 32 or more characters. |
API_CORS_ORIGIN | Optional | Allowed browser origin for /api/v1/me. | The origin of the site that will call the API. |
Optional AI (OpenAI-compatible)
| Variable | Required | Purpose | Where to get it |
|---|---|---|---|
OPENAI_API_KEY | Optional | Enables /api/ai/chat and /api/ai/image. | Your provider's dashboard. |
OPENAI_API_URL | Optional | API base. Defaults to https://api.openai.com. | Change it for another OpenAI-compatible provider. |
OPENAI_CHAT_MODEL | Optional | Model used for chat. Chat stays disabled without it. | Your provider's model list. |
OPENAI_IMAGE_MODEL | Optional | Model used for image generation. Image generation stays disabled without it. | Your provider's model list. |
Optional product analytics (PostHog)
| Variable | Required | Purpose | Where to get it |
|---|---|---|---|
ANALYTICS_ENABLED | Optional | Collection runs only when this is exactly true. Defaults to false. | Set it yourself. The tracker also respects Do Not Track. |
POSTHOG_KEY | Optional | PostHog project key, required when analytics is enabled. | PostHog project settings. |
POSTHOG_HOST | Optional | PostHog host. Defaults to https://us.i.posthog.com. | PostHog project settings. |
Variables that are not in the example files
| Variable | Read by | Purpose |
|---|---|---|
LOG_LEVEL | apps/web/server/index.ts, apps/admin/server/index.ts | pino log level. Defaults to info. |
VERCEL | both server/index.ts files | Set by Vercel. When present the server exports the Express app instead of calling listen. |
ADMIN_BASE_URL | apps/web/e2e/seeded-accounts.spec.ts | Where the Playwright suite finds the admin app. Defaults to http://127.0.0.1:3002. |
MCP_TEST_SUPABASE_URL, MCP_TEST_SUPABASE_SERVICE_KEY, MCP_TEST_SUPABASE_ANON_KEY | apps/web/src/mcp/supabase-store.integration.test.ts | Point the MCP storage integration test at an isolated local Supabase project. The test is skipped when they are missing. |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, TWITTER_API_KEY, TWITTER_API_SECRET | apps/database/supabase/config.toml | Referenced as env(...) by the local Supabase Auth provider sections. They configure the local Auth service, not the apps. See authentication. |
The repository does not ship an env example for apps/database or apps/email. Local Supabase reads config.toml, and the email preview server needs no configuration.