Environment Contract
Last reviewed: 2026-06-13
Maintained by: Engineering
ZGRID runs across web, mobile, API, and integration surfaces. This page is the canonical list of environment variables behind that setup.
Use placeholders only in examples and keep real values in environment managers, not in docs or committed files.
How to Read This Page
Most readers do not need every variable on every visit.
- If you want the platform-level runtime view, start with
Root API Runtime Variables. - If you work on a specific surface, jump directly to
Manager Desk,Back Office,Landing, orZGRID Mobile. - If you care about deployment posture, read
Environment ScopeandRules and Validation.
Quick Orientation
At a high level, the ZGRID environment contract is split into a few practical groups:
- Core runtime: API mode, ports, database access, token signing, and cookie policy.
- Platform protection: allowed web origins, rate-limit storage, and monitoring toggles.
- Communication and delivery: email provider settings, support addresses, and push delivery wiring.
- Product integrations: attachment storage, subscription billing, resident payment guards, and optional uptime monitoring.
- Surface-specific runtime:
NEXT_PUBLIC_*for internal web apps andEXPO_PUBLIC_*for mobile builds.
Root API Runtime Variables
These variables power the shared API layer behind every ZGRID surface.
| Variable | Required | Example local value | Used for | GitHub storage |
|---|---|---|---|---|
NODE_ENV | yes | development | runtime mode | vars |
PORT | yes | 3000 | API port | vars |
DB_HOST | yes | localhost | DB connection | vars |
DB_PORT | yes | 5433 | DB connection | vars |
DB_NAME | yes | zgrid | DB connection | vars |
DB_USER | yes | <db-user> | DB connection | vars |
DB_PASSWORD | yes | <db-password> | DB connection | secrets |
DATABASE_URL | runtime optional, deploy/script required | postgresql://<db-user>:<db-password>@<db-host>:<db-port>/<db-name> | direct DB connection string for migrations, schema tooling, and hosted validation | secrets |
JWT_ACCESS_SECRET | yes | <jwt-access-secret> | access token signing | secrets |
JWT_REFRESH_SECRET | yes | <jwt-refresh-secret> | refresh token signing | secrets |
JWT_ACCESS_TTL | yes | 15m | access token TTL | vars |
JWT_REFRESH_TTL_DAYS | yes | 7 | refresh token TTL | vars |
RATE_LIMIT_STORE | no | memory | rate-limit storage backend (memory or redis) | vars |
RATE_LIMIT_REDIS_URL | yes when RATE_LIMIT_STORE=redis | <redis-url> | shared Redis store for rate-limit state only | secrets |
WEB_ALLOWED_ORIGINS | yes | http://localhost:3001,http://localhost:3002 | allowed web origins for browser-based API access | vars |
COOKIE_SECURE | yes | false | auth cookie policy | vars |
COOKIE_SAME_SITE | yes | Lax | auth cookie policy | vars |
COOKIE_DOMAIN | no | empty | cross-subdomain cookie scope | vars |
EXPO_PUSH_ACCESS_TOKEN | no | empty locally | optional Expo push API bearer token used for outbound push delivery | secrets |
R2_ACCOUNT_ID | yes when attachment storage is enabled | <r2-account-id> | Cloudflare R2 account binding for attachment storage | secrets |
R2_ACCESS_KEY_ID | yes when attachment storage is enabled | empty locally | Cloudflare R2 access key for attachment storage | secrets |
R2_SECRET_ACCESS_KEY | yes when attachment storage is enabled | empty locally | Cloudflare R2 secret for attachment storage | secrets |
R2_ATTACHMENTS_BUCKET | yes when attachment storage is enabled | <r2-bucket-name> | R2 bucket name for attachment objects | vars |
EMAIL_PROVIDER | yes | dev_log locally, resend in staging/production | transactional email provider selection (disabled, dev_log, resend) | vars |
RESEND_API_KEY | yes when EMAIL_PROVIDER=resend | empty locally | Resend API authentication for real delivery | secrets |
EMAIL_FROM | yes when EMAIL_PROVIDER is not disabled | Zgrid <[email protected]> | transactional email sender | vars |
EMAIL_REPLY_TO | yes when EMAIL_PROVIDER is not disabled | [email protected] | transactional email reply-to address | vars |
EMAIL_SUPPORT_ADDRESS | yes when EMAIL_PROVIDER is not disabled | [email protected] | support contact shown in templates | vars |
EMAIL_ACTION_BASE_URL | yes when EMAIL_PROVIDER is not disabled | http://localhost:3001 | absolute base URL for email action links | vars |
MANAGER_DESK_URL | yes when EMAIL_PROVIDER is not disabled | http://localhost:3002 | Manager Desk origin and email URL fallback | vars |
APP_ENV | no | local | runtime environment label used in billing/payment guardrails | vars |
RESIDENT_PAYMENT_PROVIDER | no | mock locally, disabled in production unless a real provider is implemented | resident invoice payment provider selection (disabled, mock, or reserved future provider codes) | vars |
RESIDENT_PAYMENT_MOCK_ENABLED | no | true in local/staging mock flows | enables mock resident payment attempt flows when the selected provider is mock | vars |
LEMON_SQUEEZY_ENABLED | no | false locally unless intentionally testing hosted billing | enables SaaS subscription billing provider integration | vars |
LEMON_SQUEEZY_TEST_MODE | no | false locally | Lemon Squeezy test-mode toggle | vars |
LEMON_SQUEEZY_ALLOW_TEST_MODE_IN_PRODUCTION | no | false | explicit override guard for test mode in production | vars |
LEMON_SQUEEZY_API_KEY | yes when LEMON_SQUEEZY_ENABLED=true | empty locally | Lemon Squeezy API authentication | secrets |
LEMON_SQUEEZY_STORE_ID | yes when LEMON_SQUEEZY_ENABLED=true | empty locally | Lemon Squeezy store binding | vars |
LEMON_SQUEEZY_WEBHOOK_SECRET | yes when LEMON_SQUEEZY_ENABLED=true | empty locally | Lemon Squeezy webhook signature verification | secrets |
UPTIMEROBOT_API_KEY | no | empty | Back Office System Health uptime monitor checks | secrets |
UPTIMEROBOT_MONITOR_IDS | no | empty or 123,456 | optional monitor filter for System Health uptime component | vars |
SENTRY_ENABLED | no | false locally | API error monitoring toggle | vars |
SENTRY_DSN | no | empty locally | API Sentry DSN | secrets |
SENTRY_ENVIRONMENT | no | local | API Sentry environment tag | vars |
SENTRY_RELEASE | no | api@local | API release tag for issue regression tracking | vars |
SENTRY_TRACES_SAMPLE_RATE | no | 0 locally | API trace sampling | vars |
Manager Desk Runtime Variables
These values power the Manager Desk workspace. They belong in apps/manager-desk/.env.local for local work and in the hosted project settings for deployed environments.
| Variable | Required | Example local value | Used for | GitHub storage |
|---|---|---|---|---|
NEXT_PUBLIC_API_URL | yes | http://localhost:3000/api | Manager Desk API base URL | vars |
NEXT_PUBLIC_SENTRY_ENABLED | no | false locally | Manager Desk browser error monitoring toggle | vars |
NEXT_PUBLIC_SENTRY_DSN | no | empty locally | Manager Desk browser Sentry DSN | vars |
NEXT_PUBLIC_SENTRY_ENVIRONMENT | no | local | Manager Desk browser Sentry environment tag | vars |
NEXT_PUBLIC_SENTRY_RELEASE | no | manager-desk@local | Manager Desk browser release tag | vars |
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE | no | 0 locally | Manager Desk browser trace sampling | vars |
Back Office Runtime Variables
These values power the Back Office workspace. They belong in apps/back-office/.env.local for local work and in the hosted project settings for deployed environments.
| Variable | Required | Example local value | Used for | GitHub storage |
|---|---|---|---|---|
NEXT_PUBLIC_API_URL | yes | http://localhost:3000/api | Back Office API base URL | vars |
NEXT_PUBLIC_SENTRY_ENABLED | no | false locally | Back Office browser error monitoring toggle | vars |
NEXT_PUBLIC_SENTRY_DSN | no | empty locally | Back Office browser Sentry DSN | vars |
NEXT_PUBLIC_SENTRY_ENVIRONMENT | no | local | Back Office browser Sentry environment tag | vars |
NEXT_PUBLIC_SENTRY_RELEASE | no | back-office@local | Back Office browser release tag | vars |
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE | no | 0 locally | Back Office browser trace sampling | vars |
Landing Runtime Variables
These values support the public-facing ZGRID landing experience. For local landing contact-form testing they can come from your active local shell or root env source.
| Variable | Required | Example local value | Used for | GitHub storage |
|---|---|---|---|---|
RESEND_API_KEY | yes for real landing contact delivery | empty locally | Resend API authentication for landing contact delivery | secrets |
CONTACT_EMAIL_TO | no | [email protected] | landing contact recipient override; falls back to the configured default recipient | vars |
CONTACT_EMAIL_FROM | yes for real landing contact delivery | Zgrid <[email protected]> | landing contact sender identity | vars |
ZGRID Mobile Runtime Variables
These values support the resident and technician mobile experience. They belong in apps/zgrid-mobile/.env for local Expo work and in Expo/EAS project environment variables for hosted builds.
| Variable | Required | Example local value | Used for | GitHub storage |
|---|---|---|---|---|
EXPO_PUBLIC_API_URL | yes outside Expo fallback mode | http://127.0.0.1:3000/api | Mobile API base URL | n/a |
EXPO_PUBLIC_APP_ENV | no | development | Mobile runtime environment label | n/a |
EXPO_PUBLIC_EAS_PROJECT_ID | no | empty locally | Expo push token registration / EAS project binding | n/a |
EXPO_PUBLIC_ENABLE_RESIDENT_PAYMENTS | no | false | resident payments feature flag | n/a |
EXPO_PUBLIC_SENTRY_ENABLED | no | false locally | Mobile Sentry toggle | n/a |
EXPO_PUBLIC_SENTRY_DSN | no | empty locally | Mobile Sentry DSN | n/a |
EXPO_PUBLIC_SENTRY_ENVIRONMENT | no | local | Mobile Sentry environment tag | n/a |
EXPO_PUBLIC_SENTRY_RELEASE | no | mobile@local | Mobile Sentry release tag | n/a |
EXPO_PUBLIC_SENTRY_TRACES_SAMPLE_RATE | no | 0 locally | Mobile trace sampling | n/a |
Deploy-Only Variables
These variables matter only when the team is wiring hosted deployment automation.
| Variable | Required | Used for | GitHub storage |
|---|---|---|---|
VERCEL_TOKEN | yes for deploy environments | Vercel CLI deploy | secrets |
VERCEL_ORG_ID | yes for deploy environments | Vercel project binding | secrets |
VERCEL_PROJECT_ID_MANAGER_DESK | yes for deploy environments | Manager Desk deploy target | secrets |
RAILWAY_TOKEN | historical / optional | legacy Railway CLI deploy wiring where still present | secrets |
RAILWAY_PROJECT_ID | historical / optional | legacy Railway project target wiring where still present | vars |
RAILWAY_ENVIRONMENT_ID | historical / optional | legacy Railway environment target wiring where still present | vars |
RAILWAY_SERVICE_ID_API | historical / optional | legacy Railway API service target wiring where still present | vars |
Environment Scope
Local
Local development is where teams validate the ZGRID product flow without turning on every hosted integration.
Local development uses:
.env.exampleas the template- root
.envas the actual local configuration apps/manager-desk/.env.exampleas the Manager Desk local templateapps/back-office/.env.exampleas the Back Office local templateapps/zgrid-mobile/.env.exampleas the mobile local template
Landing note:
apps/landingcurrently does not ship a dedicated.env.example- landing contact-form variables can still be provided through your active local env source when you intentionally test email delivery
Email and monitoring rules for local development:
- use
EMAIL_PROVIDER=dev_log - do not add a real
RESEND_API_KEYlocally unless you intentionally want to test the real provider - local placeholders for sender/support addresses and action URLs are expected and should be replaced in hosted environments
- keep all Sentry toggles disabled locally unless you intentionally want to verify Sentry delivery from your own machine
- local example files must keep DSNs blank or placeholder-only; do not commit live DSNs to examples
- a missing Sentry DSN must not block startup; the app should simply run without Sentry
- local/test can keep
RATE_LIMIT_STORE=memory; Redis is optional there - if you intentionally use Redis-backed rate limiting locally, treat it as a functional check rather than a default requirement
Local default mindset:
- prefer placeholder or blank secrets
- keep optional hosted integrations off unless you are intentionally testing them
- use the simplest runtime path that still exercises the product flow you are working on
Staging / Production
Hosted environments should behave like product environments, not like oversized local setups.
GitHub Actions expects the same variable names in:
StagingProduction
Important:
- deploy validation uses the same variable names as runtime, but some scripts are stricter than the runtime parser
- there is no fallback mapping for required GitHub Environment values
- environment validation happens during manual deploy, not on every CI push
- hosted validation currently expects
DATABASE_URLto be present even though runtime parsing can fall back to split DB fields - staging/production must set
EMAIL_PROVIDER=resendand provide a realRESEND_API_KEY - staging/production can enable API Sentry with
SENTRY_ENABLED=trueplus a realSENTRY_DSN - staging/production landing contact delivery must provide
RESEND_API_KEY,CONTACT_EMAIL_TOif overriding the default recipient, andCONTACT_EMAIL_FROM - if attachment upload/download flows are enabled, staging/production must provide the required
R2_*settings - if Expo push delivery is enabled in the API, provide
EXPO_PUSH_ACCESS_TOKEN - if
LEMON_SQUEEZY_ENABLED=true, staging/production must provideLEMON_SQUEEZY_API_KEY,LEMON_SQUEEZY_STORE_ID, andLEMON_SQUEEZY_WEBHOOK_SECRET - production must not use
RESIDENT_PAYMENT_PROVIDER=mock - production must not enable
RESIDENT_PAYMENT_MOCK_ENABLED=true - production must not use
LEMON_SQUEEZY_TEST_MODE=trueunlessLEMON_SQUEEZY_ALLOW_TEST_MODE_IN_PRODUCTION=true - for multi-instance API deployments, prefer
RATE_LIMIT_STORE=redisplus a validRATE_LIMIT_REDIS_URL
Manager Desk hosted environments use Vercel project environment variables with the NEXT_PUBLIC_ names above.
Back Office hosted environments use Vercel project environment variables with the NEXT_PUBLIC_ names above.
Landing hosted environments use Vercel project environment variables for the contact form and localized public routes.
Mobile hosted builds use Expo/EAS environment variables with the EXPO_PUBLIC_ names above.
Current docs standard note:
- API hosting in this portal is documented as
Render - Railway-related deploy variables above should be treated as historical or optional wiring, not the current hosting standard
Hosted default mindset:
- use real environment-managed secrets only
- enable integrations only when the target surface actually depends on them
- treat staging as a product-like proving ground, not a second local setup
Rules and Validation
Secrets
These values must never be committed to the repo:
- DB passwords
- JWT secrets
- deploy tokens
- Sentry DSNs should still be treated as environment-managed values and not hardcoded in source or example files
- provider keys, storage credentials, push tokens, and private hosted URLs
Cookie Policy
If COOKIE_SAME_SITE=None is used, COOKIE_SECURE=true must also be used.
WEB_ALLOWED_ORIGINS controls which browser origins are allowed to use cookie-authenticated web flows. Keep docs at the level of allowed-origin policy and do not record bypass-oriented request details here.
Rate-Limit Store
RATE_LIMIT_STORE=memorykeeps the default in-process limiter and is the default in local/dev/testRATE_LIMIT_STORE=redisenables shared rate-limit state across API instances and requiresRATE_LIMIT_REDIS_URL- document Redis mode as a deployment scaling control, not as an abuse-tuning recipe
Production Rules
Production startup must fail if:
JWT_ACCESS_SECRETis still set to the development placeholderJWT_REFRESH_SECRETis still set to the development placeholder
When You Change the Env Contract
If you add or change an environment variable, always update:
apps/api/src/config/env.ts.env.example- environment-contract.md
- GitHub Environment
vars/secretsforStagingandProductionif it is relevant to deploy
Sentry Notes
- session replay is intentionally not enabled in the current setup
- source map upload is intentionally deferred; add it only when the deploy pipeline is ready to handle Sentry auth and release publishing safely