GenerateSaaS

Going to production

Run the pre-launch checklist - env vars, SEO flags, trusted origins, webhooks, and migrations - before your first real deploy.

A single launch checklist that walks every required env var, @repo/config flag, and external sync before a real deploy. Each step links the page with full detail.

Never pass --demo for a real deployment. It rewrites the schema step to a destructive reset && push --force that wipes the database - on every container start (node) or every build (Vercel). --demo is only for throwaway demo branches.

The checklist

Point at your own database + cache. Production runs against a database and cache you provision yourself - self-hosted Postgres/Redis or a managed provider (Neon/Supabase, Upstash). Set their connection env on the platform; docker-compose/pnpm infra is for local development only and is never part of a deploy. See Database and Caching.

Set required env vars on the platform. @repo/runtime (packages/runtime/src/env.ts) validates these at boot - the API throws on startup if any are missing or malformed. DATABASE_URL is validated separately by @repo/database.

DATABASE_URL=...         # your Postgres connection (validated by @repo/database)
BETTER_AUTH_SECRET=...   # min 32 chars
API_URL=https://...      # the backend's public URL
REDIS_URL=...            # self-hosted Redis; on Upstash use UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN instead
INNGEST_APP_ID=...
INNGEST_EVENT_KEY=...
INNGEST_BASE_URL=https://...

BASE_URL and TRUSTED_ORIGINS are optional in the schema but required in practice (see steps below). See Environment variables.

Lock in SEO + indexing. Keep config.indexable: true and fill real values so crawlers and metadata resolve.

KeyWhat it sets
config.indexabletrue enables crawling; false blocks robots (use for staging)
config.siteNameBrand name in titles and metadata
config.domainBare production domain
config.baseUrlCanonical origin (overridable via BASE_URL env)
config.seo.descriptionDefault meta description

See Marketing & SEO.

Set TRUSTED_ORIGINS to your real domains, comma-separated. It becomes config.origins - without it CORS and auth reject every browser call. Defaults to localhost origins only.

Configure payment webhooks when config.payment.enabled. Set the webhook secret for your provider and confirm price IDs match packages/config/src/pricing.ts.

ProviderWebhook env var
Stripe (config.payment.provider: "stripe")STRIPE_WEBHOOK_SECRET (+ STRIPE_SECRET_KEY)
Polar (config.payment.provider: "polar")POLAR_WEBHOOK_SECRET (+ POLAR_ACCESS_TOKEN)

See Payments.

Add captcha + analytics keys. Set TURNSTILE_SECRET_KEY when config.captcha.enabled (the public siteKey lives in config). Fill any analytics sub-keys you use. See Cookie consent and Analytics.

Sync the production Inngest app. Register the /api/inngest endpoint in the Inngest dashboard so scheduled and event-driven jobs run against your deploy. See Background jobs.

Run migrations against production. generatesaas init already wires this into the deploy: the node target prepends migrate to the owner app's start (runs on every container start); Vercel prepends it to the buildCommand. Run it manually only for an out-of-band schema apply:

pnpm --filter @repo/database migrate

See Database.

License heartbeat

A daily Inngest cron (license-heartbeat, fired at a stable per-install time of day) POSTs your license token + detected domain to generatesaas.com/api/v1/heartbeat. It is by design - opt out with the eject command rather than deleting the code.

On this page