Boring Docs
Config & Env — One Switch Per Concern
Every env var, what it does, and safe defaults for local vs prod.
In this guide
Config & Env
Copy once: cp .env.example .env.local. Prod uses .env.prod (see npm run build:prod / start:prod). Never commit secrets — .gitignore covers .env*, *.pem, atlas-credentials.env.
1. Core + auth
NEXTAUTH_URL—http://localhost:3000local; prod domain in.env.prod.NEXTAUTH_SECRET— any random 32+ chars.ADMIN_EMAILS— comma-separated owner emails. Prod: must be set (no dev fallback — seelibs/admin.js). Dev fallback:[email protected].GOOGLE_ID,GOOGLE_SECRET— optional Google OAuth. Without them, use dev login locally.EMAIL_SERVER,RESEND_API_KEY,RESEND_FROM— optional magic-link + transactional email via Resend.
2. Database (one switch)
DB_PROVIDER=sqlite
SQLITE_PATH=./db/app.db
# DB_PROVIDER=mongodb
# MONGODB_URI=mongodb+srv://<user>:<pass>@cluster/boring
# DB_PROVIDER=supabase
# SUPABASE_URL=https://<ref>.supabase.co
# SUPABASE_SERVICE_KEY=<service_role>
Rules: only service_role on server, never NEXT_PUBLIC_*. Supabase needs schema.sql run once. SQLite prod path: SQLITE_PATH=/db/app.db with a mounted volume.
3. Billing (one switch)
BILLING_PROVIDER=dodo
DODO_MODE=test
DODO_PAYMENTS_API_KEY=
DODO_PRODUCT_ID=pdt_xxx
DODO_WEBHOOK_KEY=
# STRIPE_* / LS_* alternatives documented in .env.example
Price IDs live in config/billing.js (sale, dodo.plans, stripe.plans) with env overrides. Never trust client prices — checkout pulls from the product row.
4. Analytics + delivery
ANALYTICS_ENABLED=true,ANALYTICS_DEBUG=false, optionalANALYTICS_PATHfor tests.GITHUB_ACCESS_TOKEN,GITHUB_REPO=owner/repo— optional auto-invite for buyers via/api/claim-repo.
5. App constants
Brand/prices in config/; toggles/limits/statuses in libs/config.js (DB_PROVIDER, BILLING_PROVIDER, POST_STATUSES, LIMITS). Never hardcode the same literal twice.