Skip to content

Environment Variables

The canonical, up-to-date list of every variable the app reads lives in .env.example in the repository — this page mirrors it, grouped by purpose. Copy it to .env.local for local development (see Installation) or .env for a deployment.

A missing or invalid required value fails the app at boot with a clear error (src/lib/envCheck.ts), rather than a confusing runtime failure later.

Church identity, branding, letters, receipts, and accounts are configured in-app (Settings), not through environment variables — see Settings and Branding. A few of these also have env fallbacks used until an admin fills in the Settings form (see below).

No trailing comments after any value, active or commented out — docker --env-file keeps them as part of the value once a line is uncommented.

Variable Purpose
DATABASE_URL PostgreSQL connection string. Add ?sslmode=require for a remote database.
AUTH_SECRET Signs sessions and sign-in codes. Generate with openssl rand -base64 32.
AUTH_URL Public https:// URL in production. Must exactly match the URL used to reach the app, or sign-in redirects break.
ENCRYPTION_KEY Field-level encryption key. Generate with openssl rand -base64 32. Back it up separately from the database — losing it makes encrypted member data unrecoverable.
GMAIL_USER, GMAIL_APP_PASSWORD Gmail SMTP credentials (or use RESEND_API_KEY + MAIL_FROM instead) for sign-in codes (OTP), receipts, and notification emails. Required at boot unless DISABLE_OTP is set (local dev only); every other email feature still needs them regardless. The password is a Gmail app password, not the account login — see Gmail Setup.
RESEND_API_KEY, MAIL_FROM Alternative to Gmail: Resend HTTPS API key + sender address on a verified domain. Setting the key switches all mail to Resend (use where SMTP is blocked, e.g. Railway Hobby). See Gmail Setup.
Variable Purpose
SETUP_TOKEN Enables /setup (create the first ADMIN) while the database has zero users. The page disables itself once any user exists; unset it after setup.
MIGRATE_ON_START Default true: the container runs prisma migrate deploy before starting. Set false to manage migrations yourself (e.g. a separate pre-deploy job).

Used until an ADMIN fills in Settings → Church Information, which then takes precedence.

Variable Purpose
CHURCH_NAME Church name shown in the UI, emails, and documents before Settings is configured.
CHURCH_ADDRESS Postal address for receipts and letters.
CHURCH_ABN Tax / charity registration number (region-specific name — Australian Business Number by default).
CHURCH_WEBSITE Public website URL, linked from public pages.

See Regional Configuration for full detail.

Variable Purpose
APP_FY_START_MONTH Financial-year start month, 1–12 (default 7 = July).
APP_TIMEZONE IANA timezone (default Australia/Sydney).
APP_LOCALE BCP-47 locale for money/date formatting (default en-AU).
APP_CURRENCY ISO-4217 currency code, also the Stripe checkout currency (default AUD).

Applies to the UI, PDFs, emails, print pages, and the mobile browser status bar. See Settings and Branding.

Variable Purpose
THEME_PRIMARY Primary brand colour, hex #RRGGBB. Unset = neutral slate.
THEME_PRIMARY_DARK Deeper primary shade (section bands, buttons). Falls back to THEME_PRIMARY when unset.
THEME_ACCENT Accent colour for emphasis / rule lines.

See Scheduled Jobs.

Variable Purpose
IN_APP_CRON true/false forces the in-app scheduler on or off. Unset = on in production only. Set false on a host that sleeps/scales to zero and call the endpoints below instead; don’t run both.
CLEARANCE_DIGEST true enables the monthly clearance-compliance digest (1st of the month, 07:00 Sydney) in the in-app scheduler. The manual POST /api/cron/send-clearance-digest endpoint does not need it, but it does need CRON_SECRET as a bearer token.
ERROR_DIGEST true enables the weekly error digest in the in-app scheduler. Also needs GITHUB_TOKEN and GITHUB_REPO.
CRON_SECRET Bearer secret required by POST /api/cron/{send-reminders,sweep-checkouts,send-celebrations,send-clearance-digest,error-issues}. Unset = those endpoints refuse every call, disabling reminders, celebration emails, the abandoned-checkout cleanup sweep, and (if configured) the error digest.

OFF unless both are set — otherwise event registrations fall back to bank transfer (pending). Register a Stripe webhook pointed at <AUTH_URL>/api/stripe/webhook.

Variable Purpose
STRIPE_SECRET_KEY Stripe secret API key.
STRIPE_WEBHOOK_SECRET Signing secret for the Stripe webhook endpoint.

Cloudflare Turnstile. Set both or neither — the server demands a token whenever the secret is set, and the widget only renders when the site key is present.

Variable Purpose
TURNSTILE_SECRET_KEY Cloudflare Turnstile secret key.
TURNSTILE_SITE_KEY Cloudflare Turnstile site key (public). NEXT_PUBLIC_TURNSTILE_SITE_KEY is still accepted as a deprecated alias.
Variable Purpose
MEMBERSHIP_SECRETARY_EMAIL Where public membership-form alerts go. Precedence: Settings → App Settings field, then this variable, then GMAIL_USER.

Pushes an HMAC-signed webhook to an external site on event create/update/publish/delete. Off unless both are set. See Event Website Webhook.

Variable Purpose
WEBSITE_SYNC_URL Receiver endpoint (must be a public https:// URL).
WEBSITE_SYNC_SECRET Shared HMAC key, e.g. openssl rand -base64 32.

Powers the Feedback Widget and the optional weekly prod-error digest cron.

Variable Purpose
GITHUB_TOKEN Fine-grained personal access token with Issues: write on GITHUB_REPO. Unset = the feedback widget is disabled.
GITHUB_REPO Target repository, owner/repo form.
Variable Purpose
APPLICATIONINSIGHTS_CONNECTION_STRING OpenTelemetry connection string (Azure Application Insights or any OTLP-compatible collector). Unset = no telemetry.

ENCRYPTION_KEY alone acts as key v1. To rotate to a new key, in order:

  1. Promote it: set ENCRYPTION_KEY_V1 to the same value as ENCRYPTION_KEY (boot refuses a non-v1 ENCRYPTION_KEY_ID while v1 still only exists as the ENCRYPTION_KEY alias).
  2. Add ENCRYPTION_KEY_V2=<new key> and set ENCRYPTION_KEY_ID=v2.
  3. Run npx tsx scripts/rotate-encryption-key.ts --apply --yes (see Operations Scripts).

Keep the v1 key permanently — the email/mobile lookup hashes are derived from it, and it decrypts any value not yet rotated. Losing it is unrecoverable.

Variable Purpose
ENCRYPTION_KEY_V1, ENCRYPTION_KEY_V2, … Versioned keyring entries.
ENCRYPTION_KEY_ID Which version new writes encrypt under, e.g. v2.
Variable Purpose
DIRECT_URL Direct (non-pooled) database connection for prisma migrate, if DATABASE_URL goes through a connection pooler. Defaults to DATABASE_URL.
  • NEXTAUTH_SECRET / NEXTAUTH_URL are read as fallbacks for AUTH_SECRET / AUTH_URL. If both the new and legacy secret are set, they must be identical or boot fails.
  • APP_URL is a fallback base URL for Stripe redirects (unused once AUTH_URL is set, which is required) and is the base URL an external scheduler uses when calling the cron endpoints.

Set by the platform / build — do not set by hand

Section titled “Set by the platform / build — do not set by hand”
Variable Purpose
CONTAINER_APP_REPLICA_COUNT Replica count reported by the hosting platform. Above 1 logs a warning — the rate limiter is in-memory, so run a single replica.
NEXT_PUBLIC_APP_VERSION, NEXT_PUBLIC_GIT_SHA Version and commit SHA shown in Settings and the sidebar. Baked into the image at build time from the Docker build args APP_VERSION / GIT_SHA.

Local development only — never in production

Section titled “Local development only — never in production”
Variable Purpose
DISABLE_OTP Skips the emailed sign-in code. The app refuses to boot with this set in production.
ALLOW_DEMO_SEED Required to let npm run db:seed create demo users — their passwords are public in this repository.
PERF_QUERY_LOG, PERF_QUERY_LOG_FILE Optional JSONL query log for performance debugging. Set both or neither.