GenerateSaaS

Environment Variables

Configure secrets in one root .env, validated by the Zod schema in @repo/runtime.

A single root .env is the source of truth for local dev, validated at boot by the Zod schema in packages/runtime/src/env.ts. Set each value once; .env is gitignored and dev-only - production vars live on your deploy platform.

Required keys

Uncommented in .env.example. The @repo/runtime schema throws on boot if any of these is missing or invalid.

VarPurpose
DATABASE_URLPostgres connection string (validated by @repo/database)
REDIS_URLCache + rate-limit store. Upstash builds require UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN instead
BETTER_AUTH_SECRETMin 32 chars, powers @repo/auth - openssl rand -base64 32
API_URLBackend origin the runtime calls (must be a URL; falls back to the *_PUBLIC_API_URL keys)
INNGEST_APP_ID / INNGEST_EVENT_KEY / INNGEST_BASE_URLBackground jobs (Background jobs)

BASE_URL and TRUSTED_ORIGINS are schema-optional but ship uncommented - see Origins and base URL. Add INNGEST_SIGNING_KEY in production (read directly by Inngest, not schema-validated).

BETTER_AUTH_SECRET and all provider keys are server secrets - never sent to the browser or third parties. Generate tokens and QR codes client-side.

Feature keys

Optional - commented out in .env.example. Each stays inert until you both set its vars and flip the matching flag in @repo/config.

Var(s)FeatureWhen needed
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRETPaymentsconfig.payment with provider: "stripe"
POLAR_ACCESS_TOKEN / POLAR_WEBHOOK_SECRETPaymentsconfig.payment with provider: "polar"
TURNSTILE_SECRET_KEYCAPTCHAconfig.captcha enabled
CONTENT_API_KEYContent APIconfig.contentApi enabled (endpoints return 503 if unset)
GOOGLE_* / GITHUB_* (+ Facebook, Discord, Twitter)Social OAuthProvider listed in config.auth (Social OAuth)
RESEND_API_KEY / AMAZON_SES_* / SMTP_*Emailconfig.email.provider (Email)
RESEND_AUDIENCE_ID / LISTMONK_*Newsletterconfig.newsletter.provider
TWILIO_* / AMAZON_SNS_*SMSconfig.sms.provider (SMS)
STORAGE_*Storageconfig.storage.provider: "s3" (Storage)
DISCORD_WEBHOOK_URL / SLACK_WEBHOOK_URL / TELEGRAM_*Admin alertsconfig.adminNotifications (each provider auto-enables when its var is set)
OPENROUTER_API_KEYTranslations (pnpm translate) + AITranslating locales + packages/content; the pre-commit hook skips without it. No config flag. See i18n.

Origins and base URL

These keys bridge env into @repo/config:

VarBacksNotes
BASE_URLconfig.baseUrl (default https://generatesaas.com)Frontend public origin - auth redirects, email links, CORS
TRUSTED_ORIGINSconfig.originsComma-separated allowlist; falls back to localhost dev ports if unset
AUTH_COOKIE_DOMAIN-e.g. .example.com - cross-subdomain cookies for the separate-backend setup

CORS and Better Auth read config.origins as-is - baseUrl is not auto-included. Your production origin must be in TRUSTED_ORIGINS or live logins redirect-fail.

How loaders work

Every app and tool reaches the same root .env, but by a different mechanism:

ContextMechanism
App dev scriptsdotenv -e .env -e ../../.env - app-local .env overrides root for conflicts
Standalone backendnode --env-file-if-exists ../../.env (dev uses tsx --env-file-if-exists)
Drizzle / Better Auth CLI (@repo/database, e.g. auth:generate)dotenv -e ../../.env
Turbo.env listed in globalDotEnv so task caches bust on change
Testsloaded explicitly in setup files

The generated schema

The CLI emits packages/runtime/src/env.ts so feature code typechecks no matter which flags you toggle.

  • Only the cache choice changes the required shape - REDIS_URL vs. the Upstash pair are non-optional.
  • Everything else (social, payment, email, SMS, storage, captcha, admin alerts) is always emitted as optional.
  • The schema validates once at module load and throws if a required key is missing or invalid.
  • Enable a feature by setting its var and flipping the @repo/config flag - never by editing this file.

Frequently asked questions

Why does my feature key do nothing? A key alone is inert. The feature also needs its flag enabled in @repo/config - see Configuration.

Does .env ship to production? No. It is gitignored and dev-only. Set vars on your deploy platform (project settings, Docker env) - see Deployment.

My production login redirects fail - why? Your live origin is almost certainly missing from TRUSTED_ORIGINS. config.baseUrl is not auto-trusted; list the origin explicitly.

On this page