Skip to main content

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, or ZGRID Mobile.
  • If you care about deployment posture, read Environment Scope and Rules 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 and EXPO_PUBLIC_* for mobile builds.

Root API Runtime Variables

These variables power the shared API layer behind every ZGRID surface.

VariableRequiredExample local valueUsed forGitHub storage
NODE_ENVyesdevelopmentruntime modevars
PORTyes3000API portvars
DB_HOSTyeslocalhostDB connectionvars
DB_PORTyes5433DB connectionvars
DB_NAMEyeszgridDB connectionvars
DB_USERyes<db-user>DB connectionvars
DB_PASSWORDyes<db-password>DB connectionsecrets
DATABASE_URLruntime optional, deploy/script requiredpostgresql://<db-user>:<db-password>@<db-host>:<db-port>/<db-name>direct DB connection string for migrations, schema tooling, and hosted validationsecrets
JWT_ACCESS_SECRETyes<jwt-access-secret>access token signingsecrets
JWT_REFRESH_SECRETyes<jwt-refresh-secret>refresh token signingsecrets
JWT_ACCESS_TTLyes15maccess token TTLvars
JWT_REFRESH_TTL_DAYSyes7refresh token TTLvars
RATE_LIMIT_STOREnomemoryrate-limit storage backend (memory or redis)vars
RATE_LIMIT_REDIS_URLyes when RATE_LIMIT_STORE=redis<redis-url>shared Redis store for rate-limit state onlysecrets
WEB_ALLOWED_ORIGINSyeshttp://localhost:3001,http://localhost:3002allowed web origins for browser-based API accessvars
COOKIE_SECUREyesfalseauth cookie policyvars
COOKIE_SAME_SITEyesLaxauth cookie policyvars
COOKIE_DOMAINnoemptycross-subdomain cookie scopevars
EXPO_PUSH_ACCESS_TOKENnoempty locallyoptional Expo push API bearer token used for outbound push deliverysecrets
R2_ACCOUNT_IDyes when attachment storage is enabled<r2-account-id>Cloudflare R2 account binding for attachment storagesecrets
R2_ACCESS_KEY_IDyes when attachment storage is enabledempty locallyCloudflare R2 access key for attachment storagesecrets
R2_SECRET_ACCESS_KEYyes when attachment storage is enabledempty locallyCloudflare R2 secret for attachment storagesecrets
R2_ATTACHMENTS_BUCKETyes when attachment storage is enabled<r2-bucket-name>R2 bucket name for attachment objectsvars
EMAIL_PROVIDERyesdev_log locally, resend in staging/productiontransactional email provider selection (disabled, dev_log, resend)vars
RESEND_API_KEYyes when EMAIL_PROVIDER=resendempty locallyResend API authentication for real deliverysecrets
EMAIL_FROMyes when EMAIL_PROVIDER is not disabledZgrid <[email protected]>transactional email sendervars
EMAIL_REPLY_TOyes when EMAIL_PROVIDER is not disabled[email protected]transactional email reply-to addressvars
EMAIL_SUPPORT_ADDRESSyes when EMAIL_PROVIDER is not disabled[email protected]support contact shown in templatesvars
EMAIL_ACTION_BASE_URLyes when EMAIL_PROVIDER is not disabledhttp://localhost:3001absolute base URL for email action linksvars
MANAGER_DESK_URLyes when EMAIL_PROVIDER is not disabledhttp://localhost:3002Manager Desk origin and email URL fallbackvars
APP_ENVnolocalruntime environment label used in billing/payment guardrailsvars
RESIDENT_PAYMENT_PROVIDERnomock locally, disabled in production unless a real provider is implementedresident invoice payment provider selection (disabled, mock, or reserved future provider codes)vars
RESIDENT_PAYMENT_MOCK_ENABLEDnotrue in local/staging mock flowsenables mock resident payment attempt flows when the selected provider is mockvars
LEMON_SQUEEZY_ENABLEDnofalse locally unless intentionally testing hosted billingenables SaaS subscription billing provider integrationvars
LEMON_SQUEEZY_TEST_MODEnofalse locallyLemon Squeezy test-mode togglevars
LEMON_SQUEEZY_ALLOW_TEST_MODE_IN_PRODUCTIONnofalseexplicit override guard for test mode in productionvars
LEMON_SQUEEZY_API_KEYyes when LEMON_SQUEEZY_ENABLED=trueempty locallyLemon Squeezy API authenticationsecrets
LEMON_SQUEEZY_STORE_IDyes when LEMON_SQUEEZY_ENABLED=trueempty locallyLemon Squeezy store bindingvars
LEMON_SQUEEZY_WEBHOOK_SECRETyes when LEMON_SQUEEZY_ENABLED=trueempty locallyLemon Squeezy webhook signature verificationsecrets
UPTIMEROBOT_API_KEYnoemptyBack Office System Health uptime monitor checkssecrets
UPTIMEROBOT_MONITOR_IDSnoempty or 123,456optional monitor filter for System Health uptime componentvars
SENTRY_ENABLEDnofalse locallyAPI error monitoring togglevars
SENTRY_DSNnoempty locallyAPI Sentry DSNsecrets
SENTRY_ENVIRONMENTnolocalAPI Sentry environment tagvars
SENTRY_RELEASEnoapi@localAPI release tag for issue regression trackingvars
SENTRY_TRACES_SAMPLE_RATEno0 locallyAPI trace samplingvars

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.

VariableRequiredExample local valueUsed forGitHub storage
NEXT_PUBLIC_API_URLyeshttp://localhost:3000/apiManager Desk API base URLvars
NEXT_PUBLIC_SENTRY_ENABLEDnofalse locallyManager Desk browser error monitoring togglevars
NEXT_PUBLIC_SENTRY_DSNnoempty locallyManager Desk browser Sentry DSNvars
NEXT_PUBLIC_SENTRY_ENVIRONMENTnolocalManager Desk browser Sentry environment tagvars
NEXT_PUBLIC_SENTRY_RELEASEnomanager-desk@localManager Desk browser release tagvars
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATEno0 locallyManager Desk browser trace samplingvars

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.

VariableRequiredExample local valueUsed forGitHub storage
NEXT_PUBLIC_API_URLyeshttp://localhost:3000/apiBack Office API base URLvars
NEXT_PUBLIC_SENTRY_ENABLEDnofalse locallyBack Office browser error monitoring togglevars
NEXT_PUBLIC_SENTRY_DSNnoempty locallyBack Office browser Sentry DSNvars
NEXT_PUBLIC_SENTRY_ENVIRONMENTnolocalBack Office browser Sentry environment tagvars
NEXT_PUBLIC_SENTRY_RELEASEnoback-office@localBack Office browser release tagvars
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATEno0 locallyBack Office browser trace samplingvars

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.

VariableRequiredExample local valueUsed forGitHub storage
RESEND_API_KEYyes for real landing contact deliveryempty locallyResend API authentication for landing contact deliverysecrets
CONTACT_EMAIL_TOno[email protected]landing contact recipient override; falls back to the configured default recipientvars
CONTACT_EMAIL_FROMyes for real landing contact deliveryZgrid <[email protected]>landing contact sender identityvars

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.

VariableRequiredExample local valueUsed forGitHub storage
EXPO_PUBLIC_API_URLyes outside Expo fallback modehttp://127.0.0.1:3000/apiMobile API base URLn/a
EXPO_PUBLIC_APP_ENVnodevelopmentMobile runtime environment labeln/a
EXPO_PUBLIC_EAS_PROJECT_IDnoempty locallyExpo push token registration / EAS project bindingn/a
EXPO_PUBLIC_ENABLE_RESIDENT_PAYMENTSnofalseresident payments feature flagn/a
EXPO_PUBLIC_SENTRY_ENABLEDnofalse locallyMobile Sentry togglen/a
EXPO_PUBLIC_SENTRY_DSNnoempty locallyMobile Sentry DSNn/a
EXPO_PUBLIC_SENTRY_ENVIRONMENTnolocalMobile Sentry environment tagn/a
EXPO_PUBLIC_SENTRY_RELEASEnomobile@localMobile Sentry release tagn/a
EXPO_PUBLIC_SENTRY_TRACES_SAMPLE_RATEno0 locallyMobile trace samplingn/a

Deploy-Only Variables

These variables matter only when the team is wiring hosted deployment automation.

VariableRequiredUsed forGitHub storage
VERCEL_TOKENyes for deploy environmentsVercel CLI deploysecrets
VERCEL_ORG_IDyes for deploy environmentsVercel project bindingsecrets
VERCEL_PROJECT_ID_MANAGER_DESKyes for deploy environmentsManager Desk deploy targetsecrets
RAILWAY_TOKENhistorical / optionallegacy Railway CLI deploy wiring where still presentsecrets
RAILWAY_PROJECT_IDhistorical / optionallegacy Railway project target wiring where still presentvars
RAILWAY_ENVIRONMENT_IDhistorical / optionallegacy Railway environment target wiring where still presentvars
RAILWAY_SERVICE_ID_APIhistorical / optionallegacy Railway API service target wiring where still presentvars

Environment Scope

Local

Local development is where teams validate the ZGRID product flow without turning on every hosted integration.

Local development uses:

  • .env.example as the template
  • root .env as the actual local configuration
  • apps/manager-desk/.env.example as the Manager Desk local template
  • apps/back-office/.env.example as the Back Office local template
  • apps/zgrid-mobile/.env.example as the mobile local template

Landing note:

  • apps/landing currently 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_KEY locally 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:

  • Staging
  • Production

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_URL to be present even though runtime parsing can fall back to split DB fields
  • staging/production must set EMAIL_PROVIDER=resend and provide a real RESEND_API_KEY
  • staging/production can enable API Sentry with SENTRY_ENABLED=true plus a real SENTRY_DSN
  • staging/production landing contact delivery must provide RESEND_API_KEY, CONTACT_EMAIL_TO if overriding the default recipient, and CONTACT_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 provide LEMON_SQUEEZY_API_KEY, LEMON_SQUEEZY_STORE_ID, and LEMON_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=true unless LEMON_SQUEEZY_ALLOW_TEST_MODE_IN_PRODUCTION=true
  • for multi-instance API deployments, prefer RATE_LIMIT_STORE=redis plus a valid RATE_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

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=memory keeps the default in-process limiter and is the default in local/dev/test
  • RATE_LIMIT_STORE=redis enables shared rate-limit state across API instances and requires RATE_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_SECRET is still set to the development placeholder
  • JWT_REFRESH_SECRET is 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 / secrets for Staging and Production if 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