"Example: flatRoles → Functions (v1 migration)"
Migrate v1 flatRoles truthfully: claims via Functions, with optional best-effort Atrium department group sync.
The old v1 flatRoles script did two real things:
- emitted claims from persisted user metadata and grants
- indirectly proved that
departmentexisted on the ZITADEL user
It did not prove that login Event payloads carry department inline.
What the v2 replacement actually is
This migration belongs on Functions, not Events.
Use:
preuserinfowhen consumers read the claim from userinfo / ID-token-adjacent flowspreaccesstokenwhen consumers read the claim from the access token- both when both surfaces matter
In OMMAX, the canonical template pair is:
flatten-rolesflatten-roles-access-token
What parity means here
Feature parity with v1 means preserving:
urn:ommax:departmentlitellm_team_idsmy:zitadel:grants
and, in the upgraded OMMAX version, optionally also:
- best-effort sync into the matching Atrium department group
That side effect must never be allowed to become more important than the claim mutation itself.
Payload reality
The observed v2 function payloads are function-shaped:
payload.userpayload.userinfopayload.user_metadatapayload.user_grants- and, for access-token flows, sometimes
payload.accessToken.sub
So the safe pattern is:
- resolve
userId - read metadata from
user_metadataif present - fall back to
ctx.zitadel.users.getMetadata(...) - build
setClaims - if desired, do best-effort Atrium group sync inside a
try/catch
Recommended OMMAX rollout
- Activate
flatten-rolesonFUNCTION:preuserinfo - Activate
flatten-roles-access-tokenonFUNCTION:preaccesstokenif the same claims are needed in access tokens - Compare the emitted claims against the old v1 result for one test user
- Only then disable the old v1 action
Why not use an Event for this?
Because flatRoles is claim-setting logic.
The right split is:
- Functions for claim mutation
- Events for after-the-fact lifecycle work
If you also want department-based Atrium groups, you have two honest options:
- fold best-effort group sync into the Function templates
- or add a separate Event script for lifecycle/login automation
Which one you choose depends on whether your real need is:
- claims parity
- onboarding
- login-time catch-up
- or all of them
Reference implementation
The authoritative reference sources in this repo are:
The templates surfaced in /admin/actions are kept in sync with those
references by test coverage.