Import / Export & Migration¶
Move a realm's entire configuration between environments as a single file — export it, commit it, and apply it elsewhere — and migrate in from an existing identity provider.
What it is¶
HelixIAM treats a realm's configuration as portable, version-controllable data. You can export a realm as one self-contained document and import it into another — to promote config from staging to production, seed a new tenant, or keep configuration in git. Secrets are never written to the file as literals: they appear as ${ENV_VAR} placeholders and are resolved from the environment on import. The same document can be dropped into a folder that the server reads on startup (config-as-code).
| Endpoint / mechanism | Purpose |
|---|---|
GET /admin/realms/{realm}/export |
Export the realm as a JSON document, secrets as ${ENV_VAR} placeholders |
POST /admin/realms/{realm}/import?onConflict=… |
Apply a realm document — overwrite, skip, or fail on conflicts |
GET /admin/realms/{realm}/export?includeAssets=true · POST /admin/realms/{realm}/import (application/zip) |
The same as an archive that also carries the theme's fonts and images — see Archives with theme files |
drop-in folder (/realms) |
Apply *.json / *.yaml realm files on startup, idempotently |
| migration import | Translate a realm-export from a previous provider — see Migrate |
The file format (v2)¶
A single self-describing document per realm. The realm id comes from the path or file name, never the document — so an export re-homes into any realm. Unset slices may be omitted; unknown fields are ignored (forward-compatible).
{
"formatVersion": 2,
"realm": { "displayName": "Gov", "captchaSecretKey": "${HELIX_GOV_CAPTCHA_SECRET}" },
"roles": [ { "name": "ledger-writer" } ],
"clientScopes": [ { "name": "billing", "claims": [ { "key": "dept", "mandatory": false } ] } ],
"clients": [ { "clientId": "billing", "grantTypes": ["authorization_code","client_credentials"],
"redirectUris": ["https://billing.example.com/callback"], "scopes": ["openid","billing"] } ],
"identityProviders": [ { "alias": "google", "protocol": "oidc",
"config": { "clientId": "…", "clientSecret": "${HELIX_GOV_IDP_GOOGLE_CLIENTSECRET}" } } ],
"flows": [ { "alias": "browser", "executions": [ /* … */ ] } ],
"samlClients": [ /* … */ ], "organizations": [ /* … */ ],
"requiredEnv": [ "HELIX_GOV_CAPTCHA_SECRET", "HELIX_GOV_IDP_GOOGLE_CLIENTSECRET" ]
}
requiredEnvlists every environment variable the file references — set these before importing elsewhere.- Excluded by design: realm signing keys (each environment mints its own) and user password / MFA secrets.
- Both JSON and YAML are accepted by the drop-in folder loader; the export endpoint returns JSON.
Export a realm¶
curl https://auth.example.com/admin/realms/acme/export \
-H "Authorization: Bearer $ADMIN_TOKEN" > acme-realm.json
The document captures the realm's configuration. Every secret is emitted as a ${ENV_VAR} placeholder — never the literal value — so the export is safe to review, commit, and share. The requiredEnv array tells you exactly which variables to provide on the target.
What's included¶
A realm export bundles every configurable slice the admin console manages:
- Realm settings — login/token/session policy, branding, CAPTCHA, password policy
- Applications, their OIDC clients, and SAML relying parties
- Per-client OIDC detail — protocol mappers, client roles, service-account role grants, resource-indicator allow-lists (RFC 8707), and authorization services (UMA resource server: resources, scopes, policies, permissions)
- Roles, client scopes (with claim mappings), and groups (hierarchy + role mappings)
- Identity providers / federation brokers and authentication flows
- Organizations and admin-RBAC role grants
- Webhooks, SCIM targets, messaging providers + message templates, and workload-identity credentials
- Users — profiles and their realm-role / group assignments
Per-client slices carry the OAuth client id as their key, so they re-home into any realm: on import each is matched to the target realm's client and applied (a slice whose client isn't present in the target is skipped, never orphaned).
Never exported — regenerate or provision these on the target: realm signing keys and their certificates (re-import your own key on the target, see Realm keys), user passwords and MFA secrets, GDPR consent ledgers and DSAR (data-subject access request) records, and all runtime state (sessions, tokens, audit log). These are per-user runtime data, not realm configuration. Every secret-bearing config field above is emitted as a ${ENV_VAR} placeholder, not a literal.
Archives with theme files¶
The JSON document references uploaded theme fonts and images by id but does not carry them. To move a branded realm to another realm or instance, use the archive: GET /admin/realms/{realm}/export?includeAssets=true returns a zip with realm-export.json, theme-assets/manifest.json and the asset files.
curl -s -b cookies.txt -o acme-realm.zip "$HELIX_URL/admin/realms/$REALM/export?includeAssets=true"
curl -s -b cookies.txt -c cookies.txt "$HELIX_URL/admin/realms/$REALM/settings" >/dev/null
CSRF=$(awk '($6=="__Host-XSRF-TOKEN"||$6=="XSRF-TOKEN")&&$7~/^r1\./{t=$7} END{print t}' cookies.txt)
curl -s -b cookies.txt -X POST -H "X-XSRF-TOKEN: $CSRF" \
-H "Content-Type: application/zip" --data-binary @acme-realm.zip \
"$HELIX_URL/admin/realms/$REALM/import"
On import every asset is uploaded again through the normal upload rules (its SHA-256 checked against the manifest), the asset URLs in the themes are rewritten to the new ids, and then the document is imported as by the JSON endpoint. A malformed, oversized or inconsistent archive is a 400 and imports nothing. Limits: the document at most 16 MB, the archive at most 40 MB compressed and uncompressed. A refused organization theme names its organization.
In the console, Import / export offers both formats with the difference explained; it checks an archive (.zip, its signature, 40 MB) before uploading, and the result table lists the theme slices and any failed entries from a 422.
Import a realm¶
# overwrite (default): upsert — existing resources are updated in place
curl -X POST "https://auth.example.com/admin/realms/acme/import?onConflict=overwrite" \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
--data @acme-realm.json
Import is an idempotent upsert by natural key (client clientId, role name, scope name, IdP alias, …): re-importing converges the target realm to the document's state without duplicating. The response summarises, per slice, how many entries were created, updated, or skipped.
Conflict modes¶
onConflict controls how an entry whose key already exists is treated:
onConflict |
Behaviour |
|---|---|
overwrite (default) |
Update existing resources in place. |
skip |
Block — leave existing resources untouched; only create new ones. |
fail |
Like skip, but each collision is listed in the response's conflicts array so you can reject the apply. |
Secret resolution¶
A ${VAR} placeholder is resolved from the server's environment. Set the variables listed in requiredEnv before importing:
export HELIX_GOV_CAPTCHA_SECRET=… # value from your secrets manager
export HELIX_GOV_IDP_GOOGLE_CLIENTSECRET=…
If a referenced variable is missing the secret is left unset (the rest of the import still applies). Strict mode — failing when any required variable is missing — is available for the folder loader below.
Config-as-code: the drop-in folder¶
Mount a directory of realm-config files and Helix applies them on startup — one file per realm, the file name is the realm id:
The server scans the folder once at startup (*.json, *.yaml, *.yml, in name order). If the folder does not exist it logs that it is skipping the load and carries on. Configure it with:
| Variable | Default | Meaning |
|---|---|---|
HELIX_REALMS_LOCATION |
/realms |
Folder scanned on startup |
HELIX_REALMS_ON_CONFLICT |
skip |
skip (never clobber a live realm) / overwrite / fail |
HELIX_REALMS_REQUIRE_ENV |
false |
true = fail a file whose required secret env var is missing |
The loader is idempotent and non-fatal: a restart re-applies cleanly (default skip makes it a no-op against an unchanged realm), and a malformed or partially-failing file is logged and skipped without stopping startup.
Neither the bundled docker-compose.yml nor the Helm chart mounts a realm folder for you. Mount one yourself, for example in your own compose override:
services:
server:
volumes:
- ./realms:/realms:ro
environment:
HELIX_GOV_WEBHOOK_AUDIT_SECRET: ${HELIX_GOV_WEBHOOK_AUDIT_SECRET} # the placeholder an export emits for webhook "audit" in realm "gov"
On start the server logs one line per file, e.g. Helix realm-config: applied 'gov.json' → realm 'gov' {… webhooks=SliceSummary[created=1, …]}.
On Kubernetes, the chart's realmConfig values mount the folder from a ConfigMap (realmConfig.configMap.name) or another volume (realmConfig.existingVolume) and set realmConfig.onConflict and realmConfig.requireEnv; pass the secrets with config.extraEnv. Charts before these values had none: there, POST the documents to /admin/realms/{realm}/import from your pipeline. See Hosting municipalities for an example.
skip and new realms
Up to and including 1.0.0-rc.12, an import with skip (the folder's default) does not create a realm that does not exist yet: the realm settings are skipped and the rest of the file lands in a realm with no settings. It is fixed in 1.0.0-rc.13. On those older releases create the realm first (PUT /admin/realms/{realm}/settings) or use overwrite.
Promote config like code
Keep the exported file in git, store the requiredEnv secrets in your secrets manager, and either POST /import from your pipeline or drop the file in the mounted folder. Because every path is idempotent, the same document converges any target realm to the same state.
Migrate from another provider¶
Moving to Helix from an existing identity provider is a paste-and-go step. In the console's Import / export screen, choose the migration import and upload (or paste) a standard realm-export JSON from your current system — Helix translates the portable slices and upserts them into the target realm: OIDC clients (grant types derived from the source), realm roles, and identity providers. The exact request shape is in the live API reference.
Users in a migration come across separately
Password hashes from another provider are not portable, so user credentials are not brought in by the migration import. Provision users via SCIM, LDAP federation, or bulk user import — then decommission the old system. (A native Helix realm export/import does carry user profiles and role/group assignments — never passwords or MFA secrets.)
Imported users have no password yet
Because exports never carry credentials, a user brought in by a realm import (or the drop-in folder) is created without a password and flagged with the UPDATE_PASSWORD required action. The account exists with all its roles and group memberships, but the person must set a password — via a reset link, the account console, an admin, or a passwordless / federated login — before their first sign-in. This is standard config-as-code behaviour: profiles ship in the file, secrets are established per environment.