Hub environment variables
Hub is a Next.js app and takes all of its configuration from environment variables — .env locally, container env when deployed. The API is configured separately through appsettings.json; its keys are under Configuration, and the URL the API puts in emails is API Hub URL, not a Hub variable.
hub/.env.example is the annotated master list. Every key on this page links to itself, so you can point a teammate or an agent at one variable.
Minimum to boot
ENDATIX_BASE_URL=https://localhost:5001 # or ENDATIX_API_URL — see API origin
SESSION_SECRET=... # openssl rand -hex 32
AUTH_SECRET=... # npx auth secret
Everything else has a default or stays off. Without an API origin, any request that reaches the API throws:
Endatix API URL is not configured. Set ENDATIX_BASE_URL (optional ENDATIX_API_PREFIX) or a complete ENDATIX_API_URL.
Behind a reverse proxy or on a non-root domain, add AUTH_URL and AUTH_TRUST_HOST — see Reverse proxy.
Where values come from
.env,.env.local— localpnpm dev. Restart the dev server to pick up a change.- Container env — a published image, wherever it runs. Restart the process.
withEndatix(nextConfig)innext.config.ts— build-time settings only, so a change needs a rebuild. Always export the wrapped config, or image hosts and storage-derivedimages.remotePatternsare never compiled in.
Runtime vs build
Next.js inlines every NEXT_PUBLIC_* variable into the client bundle at build time, and no later process.env value can override it (Next.js docs).
Hub therefore keeps its browser-facing settings on request-time names (ENDATIX_*, POSTHOG_*). The server reads them per request and projects the public subset into the React tree. One image runs every environment: point it at another API, flip debug, or replace the SurveyJS licence by changing env and restarting.
Only two things are still baked at build — NEXT_PUBLIC_BASE_PATH (subfolder deployment) and the blob hostnames compiled into images.remotePatterns.
--build-argBaking an API URL into the image ties that artifact to one environment and defeats promote-the-same-image releases. Set ENDATIX_BASE_URL at runtime.
Prefix new keys by owner, not by "this is Hub": ENDATIX_* for the Endatix API origin and Endatix product settings, and the vendor or subsystem stem otherwise — POSTHOG_*, STORAGE_*, AUTH_*, FLAG_*, SLACK_*, OTEL_*, MAINTENANCE_*, PDF_*. Whether a value is public depends on which module reads it, not on the letters in the name. Do not add new NEXT_PUBLIC_* keys.
API origin
Hub needs exactly one of two ways to reach the API — a complete URL, or an origin plus a path prefix. Neither is deprecated; pick by what your platform hands you:
- One complete URL — use
ENDATIX_API_URL. One variable carries the origin and any prefix, which is why the Helm chart sets it by default and why it suits containers and platform config screens. - An origin you compose — use
ENDATIX_BASE_URL, plusENDATIX_API_PREFIXwhen the API is not under/api. This is the pair the reverse proxy and subfolder guides use, and what.env.exampleships for local development.
Complete API URL including its prefix, http or https only — https://api.example.com/api. Hub splits the origin and prefix back out of it, so any path prefix works here without a second variable.
API origin without the path prefix — https://localhost:5001 in development, the API's internal service URL in a cluster. Combined with the prefix to form every API call.
Path prefix appended to ENDATIX_BASE_URL, ignored in ENDATIX_API_URL mode. Normalised to a leading / and no trailing /; an empty value or / means no prefix.
ENDATIX_BASE_URL takes precedence for routing, and at startup Hub writes the URL it composed into ENDATIX_API_URL only when that variable is empty. Set both to different hosts and the value you left in ENDATIX_API_URL survives — a public data-list lookup reads it directly and will call the other host while the rest of Hub talks to the first.
A value that does not parse resolves to "not configured" rather than being passed through — a typo surfaces as the error above instead of a failed fetch to a malformed host.
Request-time client config
These are the groups Platform Admin → Environment reports, in the same order — the fastest way to confirm what a running deployment actually resolved. Public keys and hosts appear there in full; the SurveyJS licence only as Set / Not set.
Debug
"true" or "false" to force debug output. Unset means on in development and off everywhere else, which is the right setting for most deployments — turn it on temporarily to diagnose a specific environment.
Experimental
Registers the SurveyJS extensions — data-list questions and custom widgets. The value must be exactly true; anything else reads as off. Set it wherever forms use data lists, or those questions render as plain inputs.
Analytics (PostHog)
PostHog's own variable names without the NEXT_PUBLIC_ prefix. Earlier Hub versions spelled these differently and there is no fallback for them — see Renamed and removed keys.
Project token (phc_…). Empty disables product analytics and PostHog feature flags. Required if you set FLAG_PROVIDER=posthog.
Capture host, and the origin server-side flag evaluation calls. Must be an absolute http(s) origin — a relative path falls back to the default. Left at the default, the browser posts through Hub's same-origin /ingest rewrite so ad blockers do not drop events; any other host (an EU project, a self-hosted instance) is called directly, because that rewrite has one destination compiled in.
UI host used to build deep links into PostHog. Cosmetic — set it when your project is not on the US cloud.
On Azure Static Web Apps, do not set POSTHOG_SECRET_KEY: it moves flag evaluation off the remote /flags endpoint.
Feature flags
Server-evaluated product gates, not the PostHog analytics isFeatureEnabled helpers. One provider wins — it is not a chain.
posthog evaluates flags through PostHog; omitted or environment reads FLAG_* variables and falls back to each flag's code default. The choice is frozen at the first flag evaluation in the process, so switching it means restarting Hub — flag values themselves still resolve per request.
One variable per flag: upper-snake the flag key, so ai-features becomes FLAG_AI_FEATURES. Booleans accept "true"; anything else reads as false. Flags ship disabled — ai-features, experimental-features, advanced-analytics, form-analytics, storage-stats, reporting-export, multi-tenancy.
posthog without a token falls back silentlyFLAG_PROVIDER=posthog with an empty POSTHOG_PROJECT_TOKEN runs the environment provider instead, with no error in the logs. Confirm the resolved provider on Platform Admin → Environment after a deploy.
reCAPTCHA
Google reCAPTCHA v3 site key — the public half of the pair, already visible in page source, so it is shown in full on the Environment page. Empty disables reCAPTCHA on public forms.
SurveyJS
SurveyJS Creator licence. Read server-side and handed only to the authenticated designer — never part of the browser projection, because that object is serialised into anonymous form pages too. Empty runs Creator unlicensed, with its non-commercial banner.
The licence is perpetual: a running deployment keeps working, and you need a new key only when you upgrade the SurveyJS packages past the window the current one covers. Store it as a secret and replace it as a config change — never rebuild the image for it.
Submitter labels
Label for the submitter filter and column. Set it to the word your product uses — "Patient", "Applicant", "Student".
Comma-separated profile fields to add as columns in the submissions grid.
PDF export
How long a submission PDF may take to render before the export gives up. Browsers land on an error page; API clients get an RFC 7807 502 with "errorCode": "pdf_render_timeout".
The default sits just under the 45-second cap Azure Static Web Apps puts on a backend request — past that the platform returns its own Backend call failure, which cannot be styled. Raise it where there is no cap (standalone container, self-hosted), lower it where the cap is shorter. An unparseable, zero or negative value falls back to 40.
Stopping the wait does not stop the render — the renderer has no abort, so an overrunning export keeps using CPU until it finishes.
Sessions and sign-in
Encrypts the Hub session cookie. Generate with openssl rand -hex 32, keep one value per environment, and rotate it to invalidate every session at once.
Session lifetime, in minutes — 900 is 15 hours, not 900 days. Shorten it for shared workstations.
Auth.js encryption key, separate from SESSION_SECRET. Generate with npx auth secret.
Public origin of this Hub, e.g. https://hub.example.com. Set it whenever Hub sits behind a proxy or an external identity provider is in play, or callbacks return to the internal hostname.
true behind a reverse proxy, so Auth.js trusts the forwarded host headers.
Keycloak and Google add AUTH_KEYCLOAK_* and AUTH_GOOGLE_* — see Authentication.
Configured elsewhere
| Area | Keys | Page |
|---|---|---|
| File uploads | STORAGE_PROVIDER, STORAGE_AZURE_*, STORAGE_S3_* | Asset storage · Azure Blob · RustFS |
| Maintenance page | MAINTENANCE_MODE, MAINTENANCE_* copy | Maintenance mode |
| Telemetry | OTEL_*, APPLICATIONINSIGHTS_CONNECTION_STRING, TELEMETRY_CONSOLE_FALLBACK, Helm otel.*, NEXT_OTEL_VERBOSE | Hub observability |
| Path prefix | NEXT_PUBLIC_BASE_PATH | Subfolder deployment |
| Sign-in providers | AUTH_KEYCLOAK_*, AUTH_GOOGLE_* | Authentication |
Smaller keys live only in .env.example: NEXT_FORMS_COOKIE_NAME and NEXT_FORMS_COOKIE_DURATION_DAYS (the cookie that resumes a partly filled form), REMOTE_IMAGE_HOSTNAMES, RESIZE_IMAGES, RESIZE_IMAGES_WIDTH, ROBOTS_ALLOWED_DOMAINS, and the SLACK_* credentials.
Containers and CI
Set these in the pod spec, Compose file, or host app settings. Process env applies on restart, never a rebuild — same digest you tested is the digest you promote. Exception: NEXT_PUBLIC_BASE_PATH (and blob hostnames in images.remotePatterns) are baked at build; changing them needs a new image. See Subfolder deployment.
Hold secrets (SESSION_SECRET, AUTH_SECRET, ENDATIX_SURVEY_LICENSE_KEY) in your platform's secret store rather than in a values file or a CI variable that lands in logs. The Helm chart maps its values onto these variables and documents its own defaults.
After a deploy, open Platform Admin → Environment to confirm what the process resolved: the API URL it will call, the flag provider in force, and whether the licence is present.
Renamed and removed keys
Two rounds of renaming. The first kept the old names working; the second did not.
NEXT_PUBLIC_* public config — deprecated in 0.7.6
Next.js inlines every NEXT_PUBLIC_* value into the client bundle at build time, so each of these could only be changed by rebuilding and redeploying the image. Hub 0.7.6 moved the public client config to request-time ENDATIX_* names, resolved per request.
They still work. At startup Hub copies each deprecated value into its current name once: a non-blank current value always wins, a whitespace-only value counts as unset on either side, and the two are never merged at read time. Migrate when convenient — the old names cannot be changed without a rebuild, which is the problem the rename fixes.
An abbreviation no one could expand, holding the value with the strongest claim to runtime replacement: rotating a SurveyJS licence should never mean rebuilding an image.
Renamed so one image can serve environments with different reCAPTCHA keys, or none.
Renamed so you can turn debug output on in one environment without a rebuild, then turn it off again.
Renamed for the same reason, and to sit under the ENDATIX_* owner stem with the rest of the product settings.
Renamed alongside the label it belongs with.
NEXT_PUBLIC_BASE_PATH is deliberately not in this list. It is a genuine Next.js build-time setting and keeps its name.
PostHog names — no fallback
The PostHog keys were renamed without a fallback of any kind: set the current name, or analytics and feature flags stay off.
Build-time inlined and under the wrong stem. PostHog owns POSTHOG_*. No fallback — empty token disables analytics and PostHog flags.
Same rename. Capture host is now POSTHOG_HOST.
Same rename. UI deep-link host is now POSTHOG_UI_HOST.