Sign in

Configuration

Everything is an environment variable — no config tables. Conventions, key variables and the plan catalog.

zmlkit keeps all configuration in environment variables — nothing is stored in the database. That makes config auditable, reproducible across environments and impossible to drift between instances. The trade-off: changing config means redeploying (or restarting dev).

The full reference lives in docs/env/web.env.example (web) and docs/env/mobile.env.example (mobile). Env is validated with Zod at boot and fails fast on invalid values.

The per-app convention

zmlkit is multi-app: one deployment can serve several products, each isolated by app_id. Config follows the same split:

  • Global variables use plain names: OPENROUTER_API_KEY, STORAGE_BUCKET, RESEND_API_KEY.
  • Per-app variables use APP_<SLUG>_<NAME>, where <SLUG> is the app slug upper-cased with - → _. App app_a → APP_APP_A_STRIPE_SECRET_KEY.
  • A per-app value overrides the global default; if unset, the global is used.
  • Apps are listed in ENABLED_APPS (comma-separated slugs) and must exist in the app table (seeded).

Key variables

  • AUTH_SECRET — session/token signing. Generate with openssl rand -base64 32. Required.
  • DATABASE_URL / DATABASE_PROVIDER — Postgres (default), MySQL, or SQLite for local dev.
  • API_BASE_URL / WEB_BASE_URL — absolute URLs, used for OAuth callbacks, webhooks and SEO (canonical/sitemap).
  • OPENROUTER_API_KEY / KIE_API_KEY — AI providers (global or per-app).
  • WAFFO_MERCHANT_ID / WAFFO_PRIVATE_KEY — Waffo Pancake payment credentials (or per-app APP_<SLUG>_WAFFO_*); WAFFO_ENVIRONMENT=test|prod selects the mode.
  • APP_<SLUG>_GOOGLE_CLIENT_ID / _SECRET (and GitHub equivalents) — OAuth, per app.
  • STORAGE_* — S3/Cloudflare R2 bucket, keys, endpoint, signed-URL TTL.
  • REDIS_URL — optional; enables distributed rate limiting across instances.
  • SENTRY_DSN / NEXT_PUBLIC_SENTRY_DSN — optional monitoring.

Minimal to boot: AUTH_SECRET + DATABASE_URL. Every capability (AI, payments, storage, email, OAuth) switches on only when its key is present, so you can add them incrementally.

What is not env: the plan catalog

Product catalog data — plans, prices, credits, Stripe price ids, Waffo Pancake product ids — is code: packages/core-domain/src/plans.ts. It is typed, versioned and shared by web and mobile. Waffo product ids can also be supplied as a plan-code map in WAFFO_PRODUCT_IDS.

{ code: 'pro_monthly', kind: 'subscription', interval: 'month',
  amount: 1900, currency: 'usd', credits: 3000, creditsValidDays: 31,
  providerRefs: {
    stripe: { priceId: 'price_...' },
    waffo:  { productId: 'PROD_...' },
  } }

Only the secrets (API keys, webhook signing secrets) go in env.

Mobile config

mobile/.env holds only public EXPO_PUBLIC_* values — API base URL, RevenueCat public keys, native OAuth client ids. Server secrets never ship to the mobile app.