Every environment variable is validated at startup using Zod schemas. If a required variable is missing or malformed, the app will crash immediately with a descriptive error — no silent failures.
API (apps/api)
The API has the most env vars since it connects to all external services. The Zod schema lives in apps/api/src/env.ts.
Database
| Variable | Type | Description |
|---|
DATABASE_URL | URL | Neon Postgres connection string |
Redis
| Variable | Type | Description |
|---|
REDIS_URL | URL | Upstash Redis REST URL |
REDIS_REST_TOKEN | String | Upstash Redis authentication token |
Supabase Auth
| Variable | Type | Description |
|---|
SUPABASE_URL | URL | Supabase project URL |
SUPABASE_PUBLISHABLE_KEY | String | Supabase anon (publishable) key |
SUPABASE_SECRET_KEY | String | Supabase service role key (server-side only) |
SUPABASE_WEBHOOK_SECRET | String | Secret for verifying Supabase auth webhook payloads |
File Storage
| Variable | Type | Description |
|---|
BLOB_READ_WRITE_TOKEN | String | Vercel Blob read/write token for CSV uploads and exports |
Background Jobs
| Variable | Type | Description |
|---|
INNGEST_EVENT_KEY | String | Inngest event key for sending events |
INNGEST_SIGNING_KEY | String | Inngest signing key for webhook verification |
| Variable | Type | Description |
|---|
ANTHROPIC_API_KEY | String | Anthropic API key for Claude Haiku column mapping |
Email
| Variable | Type | Description |
|---|
RESEND_API_KEY | String | Resend API key for transactional emails |
RESEND_FROM_EMAIL | Email | Default sender address (defaults to noreply@folksbase.dev) |
Encryption
| Variable | Type | Description |
|---|
ENCRYPTION_KEY | 64-char hex | AES-256-GCM key for encrypting secrets at rest (e.g., user Resend API keys) |
Generate one with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
App Configuration
| Variable | Type | Default | Description |
|---|
APP_URL | URL | http://localhost:3000 | Backend URL (used internally) |
FRONTEND_URL | String | http://localhost:3000 | Frontend origin(s) for CORS. Comma-separated for multiple origins |
PORT | Number | 3001 | Port the API server listens on |
FRONTEND_URL supports multiple origins and wildcards:
# Single origin
FRONTEND_URL=https://folksbase.joselito.dev
# Multiple origins
FRONTEND_URL=https://folksbase.joselito.dev,https://staging.folksbase.dev
# Wildcard subdomain
FRONTEND_URL=*.folksbase.dev
Each origin is validated at startup to start with http://, https://, or *..
Web App (apps/web)
The frontend has three public env vars. The Zod schema lives in apps/web/src/env.ts.
| Variable | Type | Description |
|---|
NEXT_PUBLIC_API_URL | URL | API base URL (e.g., http://localhost:3001/api) |
NEXT_PUBLIC_SUPABASE_URL | URL | Supabase project URL |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | String | Supabase anon key |
All web app env vars are prefixed with NEXT_PUBLIC_ because they’re exposed to the browser. Never put secrets here.
Database (packages/db)
| Variable | Type | Description |
|---|
DATABASE_URL | URL | Neon Postgres connection string (validated at module load) |
This is the same DATABASE_URL used by the API — the packages/db package validates it independently when the module is imported.
E2E Tests (Playwright)
These are only needed when running Playwright tests. They’re read via process.env directly (not Zod-validated).
| Variable | Type | Default | Description |
|---|
E2E_USER_EMAIL | String | — | Test user email (real Supabase credentials) |
E2E_USER_PASSWORD | String | — | Test user password |
PLAYWRIGHT_BASE_URL | URL | http://localhost:3000 | Base URL for E2E tests |
Rules for Adding New Env Vars
- Add the variable to the Zod schema in the relevant
env.ts file
- Add it to the corresponding
.env.example with a comment
- Never commit
.env files — they’re in .gitignore
- Always import from the typed module (
import { env } from '@/env'), never use process.env directly