Environment Variables
Find which settings you need now, where to put them, and what to configure before launch.
Use this page when you need to add a service or prepare a deployment. You do not need to fill every integration key to explore the local app with seed accounts.
Before You Start
Complete Quick Start. Init creates the local files and signing secrets for you. Keep your app's .env.example open as the field inventory, but put real values in .env.local, not the example file.
What to Configure Now
| Stage | Configure |
|---|---|
| First local sign-in | Generated auth/newsletter secrets; development Postgres; seed accounts |
| Brand customization | Default locale, public support address, local site origin |
| Test email, OAuth, payment, or upload | Only the credentials for the feature you are testing |
| Deploy | Production origin, database/bindings, stable signing secrets, real mail, and every enabled integration |
Local console mail does not deliver messages. Missing integrations can fail when used even though the homepage and seed login work. Product display settings live in packages/config/src/index.ts; credentials are environment values.
Application Environment
| Configuration | Web |
|---|---|
| Local files | apps/web/.env.local |
| Database | DATABASE_URL; pooled Postgres |
| Storage | S3-compatible S3_* credentials |
resend or cloudflare; local console |
pnpm muse:init --yes --app web --database-url <postgres-url>
pnpm db:seed
pnpm devInit preserves existing values and generates missing signing secrets. Do not use --force on a configured environment.
Core Variables
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SITE_URL | Origin, links, and OAuth callbacks; use the real production HTTPS origin |
NEXT_PUBLIC_DEFAULT_LOCALE | Default en or zh |
NEXT_PUBLIC_EMAIL_SUPPORT | Public support address |
BETTER_AUTH_SECRET | Session signing; missing in production runtime is a hard error |
BETTER_AUTH_TRUSTED_ORIGINS | Comma-separated additional trusted origins |
BETTER_AUTH_ENABLE_TEST_UTILS | Automated test helpers; keep false normally |
GITHUB_CLIENT_ID/SECRET, GOOGLE_CLIENT_ID/SECRET | Optional OAuth; missing credentials hide the provider |
NEXT_PUBLIC_GOOGLE_ONE_TAP_CLIENT_ID | Optional Google One Tap |
NEXT_PUBLIC_TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY | Both required to enable captcha |
MAIL_FROM, MAIL_FROM_NEWSLETTER, CONTACT_FORM_TO | Transactional/newsletter sender and contact inbox |
NEWSLETTER_TOKEN_SECRET | Unsubscribe HMAC signing, without an auth-secret fallback |
MUSE_SEED_EMAIL, MUSE_SEED_PASSWORD | Local seed account domain and password, not production provisioning |
Mail Credentials
Resend needs MAIL_PROVIDER=resend and RESEND_API_KEY. Cloudflare Email Sending needs CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, and a verified sending domain. Local console prints messages without delivery and is rejected in production. Missing credentials can warn at startup but still break OTP delivery.
Billing
BILLING_DEFAULT_GATEWAY accepts muse_creem, muse_stripe, muse_dodo, or muse_waffo. Empty falls back across configured gateways. Never put secrets in NEXT_PUBLIC_*.
- Creem:
CREEM_API_KEY,CREEM_WEBHOOK_SECRET,CREEM_ENVIRONMENT - Stripe:
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET - Dodo:
DODO_PAYMENTS_API_KEY,DODO_PAYMENTS_WEBHOOK_SECRET,DODO_PAYMENTS_ENVIRONMENT - Waffo:
WAFFO_MERCHANT_ID,WAFFO_PRIVATE_KEY,WAFFO_ENVIRONMENT; optionalWAFFO_DEBUG,WAFFO_CUSTOMER_PORTAL_URL - Products:
<GATEWAY>_PRODUCT_ID_PRO_MONTHLY,<GATEWAY>_PRODUCT_ID_PRO_YEARLY,<GATEWAY>_PRODUCT_ID_LIFETIME, withCREEM,STRIPE,DODO, orWAFFOprefixes
Store a Waffo private key on one line with \n for newlines. It signs API requests; webhooks use SDK verification, without a separate Waffo webhook secret. See gateway guides for sandbox/production environment values.
Object Storage
Set S3_ENDPOINT, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY. Optional settings are S3_AVATARS_BUCKET, S3_AVATARS_PROXY_URL, and S3_REGION. See storage.
Build and Runtime Boundaries
NEXT_PUBLIC_* values are inlined into clients and require a rebuild when changed. Inject server secrets on the target platform. Do not copy a whole local file containing seed credentials and development URLs into production.
src/env/readiness.ts collects errors/warnings, throws hard errors in production runtime, and does not abort the build phase for those same hard errors. Use SKIP_ENV_VALIDATION=true only for necessary secretless CI builds, never to hide missing production config. A successful build does not prove auth, mail, or database readiness.
Keep package ./env, the selected app schema, and .env.example aligned when adding variables. Platform differences are intentional. Never commit .env.local or real secrets.
Check Your Result
Restart the dev server after editing environment files. Production needs host secrets. Open the app and test the specific integration you enabled, such as a delivered email or sandbox checkout. Rebuild after changing NEXT_PUBLIC_* on a deployed app.
Common Problems
| Problem | Fix |
|---|---|
| Local settings changed but behavior did not | Check the selected app's file and restart its server |
| Live links still point at localhost | Correct the build-time public origin and rebuild |
| Production starts but a feature fails | Read server/provider errors and check that feature's required values, not just build success |
Follow Email, Payments, and Storage for service-specific walkthroughs.