SsuperslateDocs
Build with the boilerplate

Add an environment variable

Add configuration through typed validation, examples, deployment, and documentation.

Golden path: add an environment variable

When to use

Use this path when runtime behavior genuinely varies by deployment or requires a credential, external endpoint, resource path, or operational limit. Prefer code constants for invariants and database/product configuration for values a user should change without redeploying.

Classify the variable before editing:

  • required boot input;
  • optional integration credential;
  • safe value with a deterministic default;
  • deployment-only resource path;
  • public web build input.

Files and boundaries

Server variables:

  • apps/server/src/config/env.ts: Zod declaration, normalization, production checks, and Config.
  • apps/server/.env.example: safe example plus required/optional behavior.
  • apps/server/src/test-setup.ts: deterministic test values and external-side-effect prevention.
  • apps/server/Dockerfile, apps/server/railway.toml, CI, and deployment docs when an owner must map the value.
  • packages/create-app/src/core.ts and tests when scaffolding must generate or rewrite it.

Web variables:

  • apps/web/.env.example;
  • apps/web/src/configs/config.ts or a validated public-config boundary;
  • Vercel/build configuration and buyer documentation.

All VITE_* values are public build output. Secrets belong only on the server.

Procedure

  1. State the owner, sensitivity, required/optional status, default, environments, rotation behavior, and no-key behavior.

  2. Add the variable to envSchema in apps/server/src/config/env.ts. Validate URLs, enums, positive integers, header names, and minimum secret lengths at the boundary rather than at each call site.

  3. Map the inferred value into Config and read it through config(). Do not read process.env.NEW_VALUE throughout domain code.

  4. Add a safe .env.example entry. Use a placeholder, never a copied real value. Explain exactly what remains usable when an optional key is absent.

  5. If the variable is a third-party integration, preserve zero-key local startup with an actionable disabled result or a deterministic local adapter. Production may deliberately require the key, but that check belongs in the validated config boundary.

  6. Update src/test-setup.ts so a developer .env cannot send email, post Slack messages, call a paid provider, or enable an ambient integration during tests.

  7. Update every deployment owner:

    • Railway or Dokploy server runtime for server-only values;
    • Vercel build/runtime only for explicitly public web values;
    • Docker ENV only for safe defaults, never image-baked secrets;
    • CI with test-only values where the path must execute.
  8. If create-app must generate the value, update the environment generator, scope/brand behavior if applicable, and its exact tests. Required secrets must be unique per scaffold.

  9. Add positive, missing, malformed, and production-required tests. For resource paths, test the supported layouts and make an explicit missing override fail fast.

  10. Scan the diff and logs for copied values, then run a custom buyer scaffold.

Verification

vp check
vp run --filter @app/server typecheck
bun test --cwd apps/server
vp run -r test
vp run -r build
git diff --check

Then verify both configurations that matter:

  1. the variable present with a safe test value;
  2. the variable absent, proving the documented default or actionable startup failure.

For a public web variable, inspect the production bundle and assume every value is readable by a buyer or end user. For a secret, scan output for the exact test value without printing a real secret.

Security constraints

  • Never expose credentials through VITE_*, response JSON, health endpoints, logs, exception messages, analytics, source maps, Linear, or committed .env files.
  • Do not use production credentials in local tests. Test setup must override values Bun loaded from a developer .env.
  • Generate secrets with a cryptographically secure source and document rotation/revocation.
  • Validate callback origins, provider URLs, proxy headers, and resource paths rather than accepting arbitrary strings.
  • Treat a secret that appeared in git history or tool output as compromised; removal from the latest file is not remediation.

Failure modes

  • The Zod schema accepts a value but Config forgets to map it, or code bypasses Config.
  • .env.example, create-app, Docker, Railway, Dokploy, Vercel, and CI disagree on the variable name.
  • An optional integration crashes basic local setup instead of degrading clearly.
  • A default silently enables billable network traffic, weakens auth/security, or changes production behavior.
  • A web-prefixed value leaks a server credential into JavaScript.
  • A compiled binary depends on a cwd-relative resource and source tests never exercise the packaged layout.
  • Tests inherit a real developer key and create an external side effect.

Rollback and diagnosis

Remove a newly optional variable only after all consumers tolerate absence. For a renamed variable, support both names for a documented transition or make the breaking release explicit; do not silently reinterpret an existing value. If startup fails, use the Zod field error and deployment owner to diagnose mapping before weakening validation.

The MIGRATIONS_DIR and EMAIL_TEMPLATES_DIR implementation is the reference for explicit resource overrides: verified layouts work without configuration, an explicit invalid path is authoritative and fatal, and the compiled binary is tested from repository root.

Acceptance criteria

  • Sensitivity, owner, required/optional behavior, default, and no-key behavior are documented.
  • Schema, Config, examples, tests, deployment mapping, and scaffold generation agree.
  • Present, absent, malformed, and production-required cases behave intentionally.
  • No secret is client-visible, logged, committed, or used by tests.
  • Root verification and a generated buyer artifact pass.

Agent prohibitions

  • Do not read new environment variables ad hoc outside the config boundary.
  • Do not make a third-party key mandatory for basic local setup without an explicit release-contract decision.
  • Do not put secrets in VITE_*, Docker image layers, examples, tests, fixtures, comments, or issue trackers.
  • Do not weaken validation merely to make one environment boot.
  • Do not add a variable without updating its deployment owner and removal/rotation behavior.
  • Do not call the path complete after typecheck without testing present and absent states.

On this page