The backend reads configuration in two ways.
  1. The validated schema. apps/core-api/src/config.ts defines a Zod schema and loadEnv() passes it to defineEnv from @perform/config. defineEnv loads .env with dotenv, validates process.env, prints every failing key to stderr and exits with code 78 when a required value is missing or invalid.
  2. Direct reads in the ported packages. @repo/auth, @repo/payments, @repo/mail, @repo/storage, @repo/utils, @repo/api and @repo/database read process.env themselves. These variables are not validated at startup. A missing value shows up only when the feature runs.
The ESLint rule perform-internal/no-direct-process-env blocks direct reads in apps/ and in the @perform/* packages. It is switched off for the ported @repo/* packages.
The READMEs refer to a .env.example file. The backend repository does not contain one at the time of writing, although .gitignore allows it. This page is the reference. Never commit a real .env.

Validated by config.ts

Core

Rate limiting

Authentication

Trainee app update prompt

WhatsApp (SmartSend)

AI

Storage and uploads

Imports and misc

Read directly by packages

These are not in the schema. Set them when you use the feature.

@repo/auth

@repo/auth also reads SERVICE_AUTH_SECRET, SMARTSEND_BASE_URL, SMARTSEND_API_KEY, SMARTSEND_OTP_API_KEY and TRAINEE_OTP_DEV_MODE directly.

@repo/payments

@repo/mail

The provider exported from packages/mail/src/provider/index.ts is ZeptoMail. The other provider files remain in the package but are not exported.

@repo/storage

Reads the R2_* variables above. For each one it falls back to the older S3_ENDPOINT, S3_REGION, S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY names. NEXT_PUBLIC_AVATARS_BUCKET_NAME names the avatars bucket.

Prisma CLI

MCP stdio server (apps/mcp-server)

This binary runs on a coach’s machine, not on the server.

One-off scripts

Scripts under apps/core-api/src/scripts and packages/database/scripts validate their own small schemas. Examples are RAPIDAPI_KEY, ASCENDAPI_HOST and ASCENDAPI_BASE_URL for the exercise catalog import, plus flags such as APPLY, LIMIT, ONLY, STUDIO_SLUG and CONCURRENCY that individual scripts document at the top of the file. They are not needed to run the API.

Variables that appear in old env files but are not read

GREEN_API_BASE_URL, GREEN_API_INSTANCE_ID and GREEN_API_TOKEN are listed in older docs and env files. No code in apps/ or packages/ reads them. You can leave them out.

Rules

  • Add a new variable to the schema in config.ts so it is validated at boot, and give it a safe default when the feature is optional.
  • Read it from ctx.env in modules. Do not read process.env in apps/core-api.
  • Never give a secret a real default in code.
  • NEXT_PUBLIC_SAAS_URL keeps its Next.js style name in the backend because the ported packages still read it under that name.