# Which Problems Are Solved
Expose impersonating actor to `preAccessToken` and `preUserinfo`
actions.
# How the Problems Are Solved
Provide the `actor` information already present in the OIDC session
model of JWT token to the relevant actions. Both goja-based actions "v1"
and webhook based execution targets carry the actor information now.
Actor remains null in case of non-impersonated tokens. Actor may contain
nested actors to display a delegation chain of impersonators.
# Additional Changes
- dba6261c8ed0dfd00ffde6193910b1405b17432f: refactor `userinfoFlows` to
reduce complexity, add test coverage and solve a couple of potential
bugs.
- `getClientId` in actions was previously undocumented. Added to
documentation.
- A skill that reproduces manual testing using webhook.site or a local
sink.
# Additional Context
- Closes https://github.com/zitadel/zitadel/issues/12097
---------
Co-authored-by: Claude Sonnet 5 <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Wim Van Laer <[email protected]>
# Which Problems Are Solved
- Native iOS/Android passkeys need OS trust files on the ZITADEL domain;
without them, Associated Domains / App Links verification fails.
- Operators could not configure iOS Team ID + Bundle ID or Android
package name + SHA-256 fingerprints on OIDC apps.
- `/.well-known/apple-app-site-association` and
`/.well-known/assetlinks.json` were not served from application config.
# How the Problems Are Solved
- Add iOS/Android app-link fields on OIDC app create/update (Application
API v2 + Management), with validation.
- Persist and project those fields; query active app-link configs
instance-wide.
- Serve AASA (`webcredentials`) and Digital Asset Links
(`get_login_creds`) from well-known paths, with configurable
`Cache-Control` and fingerprint normalization at serve time.
- Console UI to edit the fields, with links to the well-known endpoints.
- Operator docs for configuration, endpoints, caching, and verification.
# Additional Changes
- Document on API fields that well-known responses may be HTTP-cached
and platform verifiers may delay propagation.
- Runtime config: `WellKnown.AppLinksCacheControlMaxAge` (default `5m`;
`0` → `no-store`).
# Additional Context
- Closes#12497
- Implemented and reviewed as stack:
- #12531 API contract
- #12532 storage wiring
- #12536 well-known endpoints
- #12537 console
- #12547 docs
---------
Co-authored-by: Cursor <[email protected]>
# Which Problems Are Solved
Since #10666 (v4.1+, backported to v4.x), metadata values set through
actions v1 (`api.metadata.push` and `api.v1.user.appendMetadata`) are
always JSON-encoded via `json.Marshal`. This made the write path
consistent with the JSON-based read path, but removed the ability to
store raw (unencoded) metadata values:
- A scalar string is now always stored quoted (`"de"` instead of `de`).
- The previous byte-array convention (mapping a string to an integer
array in the script, handled by `mapBytesToByteArray` introduced in
#5526) now stores the literal integer-array text (e.g. `[100,101]`)
instead of the raw bytes.
Customers migrating from v3.x whose downstream systems base64-decode
metadata values from tokens and expect raw bytes have no way to produce
them anymore — changing the consuming system is not always possible.
Reverting the default is not an option either, as clients that adopted
actions v1 on v4.x now rely on the JSON encoding.
# How the Problems Are Solved
- Adds a new, opt-in function `api.v1.user.appendMetadataRaw(key,
value)` to the actions v1 login flows (external / internal
authentication post authentication and pre creation), next to the
existing `appendMetadata`.
- The value is stored as raw bytes without JSON encoding:
- a string is stored as its plain UTF-8 bytes (`de`, not `"de"`)
- byte arrays (`Uint8Array` or a plain array of integers 0-255, the old
convention) are stored as-is, so existing v3 scripts using a
string-to-byte-array helper only need to switch the function name
- other types (and empty values) throw an error
- The existing `appendMetadata` and `api.metadata.push` behavior remains
byte-for-byte unchanged.
# Additional Changes
- Documented `appendMetadataRaw` (and the JSON encoding behavior of
`appendMetadata`) in the external and internal authentication actions
docs.
- Added unit tests for the new function (through a real goja runtime)
and a test locking in the existing `appendMetadata` JSON-encoding
behavior.
# Additional Context
- Regression introduced as a side effect of #10666 (which fixed#10470);
the raw byte handling was originally introduced in #5526.
- Reported by a customer upgrading from v3.4.x to v4.16.x, whose
PostAuthentication action maps token payload claims into user metadata.
- Requires backport to v4.x.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 <[email protected]>
# Which Problems Are Solved
#12313 lets clients register themselves as OIDC applications (RFC 7591),
but a dynamically registered client has no way to manage itself
afterwards:
- The registration response returns no `registration_access_token` and
no `registration_client_uri`, which RFC 7591 and 7592 clients (including
the MCP SDKs) expect in order to manage their own registration.
- There is no way to read back, update or delete a registration without
an operator acting on the client's behalf through the Console or the
Management API. A client that needs to rotate a redirect URI has to ask
a human.
# How the Problems Are Solved
Implement the [OAuth 2.0 Dynamic Client Registration Management Protocol
(RFC 7592)](https://datatracker.ietf.org/doc/html/rfc7592): `GET`, `PUT`
and `DELETE` on `/oauth/v2/register/{client_id}`, wired through the same
`op.WithSetRouter` hook as the registration endpoint.
## Control plane: the same security settings, no new switch
The management endpoints are gated on the
`DynamicClientRegistrationSettings.enabled` introduced in #12313. No new
setting, no feature flag: a client that may register may manage what it
registered. When registration is disabled the endpoints answer `404`,
exactly as `POST /oauth/v2/register` does.
`allow_unauthenticated` selects the registration mode and does not apply
here, because the registration access token authorizes these endpoints
in both modes.
## Authorization: the registration access token, and nothing else
RFC 7592 §3 defines the credential as the registration access token
issued at registration and bound to a single client, not a user
credential. So a client manages only itself, proving it with the token
it was handed:
| | Registration (`POST`) | Management (`GET` / `PUT` / `DELETE`) |
| --- | --- | --- |
| Credential | Access token, or none in open mode | Registration access
token |
| Permission | `project.app.register_dynamic` in token mode | None |
Neither `project.app.register_dynamic`, which governs who may *create*
clients, nor `project.app.write` applies to the management endpoints.
One authorization path per endpoint is easier to reason about than two,
and there is no user identity to check against in the first place.
Operators keep managing dynamically registered clients through the
Management API and the Console, like any other application; they cannot
obtain a client's registration access token, by design.
Status codes: a missing, malformed or non matching token is `401`; a
client that no longer exists is `404`. The token's `client_id` is
checked against the path before any lookup, so a token bound to another
client is rejected without touching the database.
## The token itself
- **Stored like a client secret.** Only its passwap hash is persisted,
through a new
`project.application.config.oidc.registration_token.changed` event; the
plain token leaves the server exactly once, in the response that issues
it.
- **Bound to its client and organization** through authenticated
encryption with the instance key, the same mechanism as refresh tokens.
The opaque token handed to the client encrypts
`client_id:org_id:secret`, so the endpoint recovers the binding before
checking the secret against the stored hash.
- **Verified in O(1)** against the hash on the projection, folded into
the client lookup the handler already does, with a strongly consistent
fallback to the eventstore so a token that was just rotated is accepted
before it is projected.
- **Rotated on update** (RFC 7592 §4.4) and invalidated implicitly when
the client is deleted. A read does not rotate it. Every update rotates,
including one that changes no metadata, so a client always gets a usable
token back.
## Reusing the existing persistence
A small refactor extracts the shared OIDC application creation and
update logic (`pushOIDCApplication` gains a variadic `extraEvents`, and
`oidcApplicationChangeEvent` is lifted out of `UpdateOIDCApplication`),
so the dynamic commands reuse it without the permission check. The
registration token event is pushed atomically with the application, so a
registered client is never left unmanageable.
# Additional Changes
- Setup step **74** adds the `registration_token` column to the
`apps7_oidc_configs` projection, with a reducer for the new event
modelled on the client secret. Applications are not part of the
relational storage projection, so there is no second write path to keep
in sync.
- The registration response (`POST`) now also carries
`registration_access_token` and `registration_client_uri`.
- Docs: a "Manage a registration" section in the dynamic client
registration guide covering the three operations, the authorization
model, token rotation and the new limitations, plus the management
endpoints on the OIDC endpoints page.
- Unit tests for the command layer (issue, verify, rotate, update,
delete), the projection reducer, and the token binding and response
mapping. An integration test covers the full lifecycle (register, read,
update with rotation, delete, read again with `404`), a token bound to
another client, and the endpoints not being served when registration is
disabled. None of the integration calls carries a user access token,
which pins down that the management endpoints require no permission of
their own.
# Additional Context
- Part of the MCP authorization effort in #9810. Follow-up to #12313
(RFC 7591), which is now merged; this branch has been rebased on `main`
and carries the settings and authorization model agreed there.
- **Open question for the review**: the authorization model above
(registration access token only, no operator path through these
endpoints) is the one I proposed when #12313 was reviewed, but it was
never explicitly confirmed. If you would rather have `project.app.write`
also grant access to a client in the operator's own organization, that
is a contained change in `authorizeClientManagement`.
- Rotation is eventually consistent: right after an update the previous
token may keep working for a brief moment until the rotation is
projected, while the new token works immediately through the eventstore
fallback. This matches how ZITADEL validates access tokens, where
revocation is likewise eventually consistent. It is documented in the
guide and asserted as such in the integration test.
- A database migration is introduced for the new projection column. No
proto change and no new feature flag.
- Verified locally: full build, `go vet` including `-tags integration`,
`golangci-lint` with 0 issues, and the unit suites for every touched
package. As before, I cannot run the integration suite here, so CI
remains the check on it.
# Which Problems Are Solved
ZITADEL cannot currently be used as an OAuth 2.0 Authorization Server by
clients that need to register themselves at runtime:
- There is no [OAuth 2.0 Dynamic Client Registration (RFC
7591)](https://datatracker.ietf.org/doc/html/rfc7591) endpoint, and no
`registration_endpoint` is advertised in the discovery document.
- This blocks [Model Context Protocol
(MCP)](https://modelcontextprotocol.io) clients (Claude Desktop,
claude.ai, Cursor, the MCP SDKs), whose OAuth 2.1 authorization profile
expects an Authorization Server that supports dynamic client
registration. They self-register before any user context exists and only
then start the authorization code + PKCE flow.
# How the Problems Are Solved
## Control plane: instance security settings
No feature flag and no runtime config. A
`DynamicClientRegistrationSettings` message is nested under the instance
`SecuritySettings` in `settings/v2`, next to `embedded_iframe` and
`enable_impersonation`:
| `enabled` | `allow_unauthenticated` | Behaviour |
| --- | --- | --- |
| `false` (default) | — | `POST /oauth/v2/register` returns `404`,
`registration_endpoint` absent from discovery |
| `true` | `false` (default) | Token-gated registration, client homed in
the token's organization |
| `true` | `true` | Open registration for MCP hosts, client homed in the
instance default organization |
Both values are carried on `authz.Instance` and loaded with the
instance, exactly like `EnableImpersonation`, so the discovery document
and the endpoint read them per request without an extra query.
`allow_unauthenticated` implies `enabled` in both implementations, so a
single setting closes the endpoint.
Adding the two columns bumps the projection table to
`projections.security_policies3`, following what the impersonation
setting did for `security_policies2`.
## Authorization: a dedicated permission, not `project.app.write`
- New org-scoped permission **`project.app.register_dynamic`**, granted
by default to `ORG_OWNER`, `IAM_OWNER` and `IAM_ORG_MANAGER` — in both
`InternalAuthZ` and `SystemAuthZ`, mirroring where `project.app.write`
already lives.
- New built-in role **`ORG_DYNAMIC_CLIENT_REGISTRAR`** carrying only
that permission, so a service user can self-register clients without
gaining write access to the organization's existing applications.
- Open mode (`allow_unauthenticated = true`) requires no token and no
permission: that is the MCP self-register path.
- A valid token whose user lacks the permission is answered with **`403
insufficient_scope`** (RFC 6750 §3.1) rather than `401 invalid_token`,
so a caller can tell "your token is bad" from "your token may not do
this". `verifyAccessToken` also reports invalid tokens as permission
denied, so the authorization failure is marked explicitly to keep the
two apart.
The check runs once, at the endpoint, right after the token is verified
and before any state is created, so it also gates the auto-provisioning
of the organization's DCR project. The permission itself lives in the
command layer next to the others
(`Commands.CheckPermissionRegisterDynamicClient`). As the OIDC endpoints
are not behind the authorization interceptor, the caller identity is
derived from the verified token, the way token exchange does for the
impersonating actor.
## Registration itself
- **Registered clients are ordinary OIDC applications.** The endpoint
reuses the existing application-creation events
(`NewApplicationAddedEvent` and `NewOIDCConfigAddedEvent`), so the whole
token, authorization and introspection flow keeps working unchanged.
They are stored in a dedicated, auto-provisioned project named `ZITADEL
DCR` per organization (name lookup), so they do not pollute the
`IAMProject` or other projects.
- Input is validated with the existing `GetOIDCV1Compliance` rules;
`private_key_jwt` and `jwks`/`jwks_uri` are rejected with
`invalid_client_metadata`.
- **Endpoint wiring without forking the library.** `NewServer` calls
`op.RegisterServer` instead of `op.RegisterLegacyServer`, so the new
route can be added through the same `op.WithSetRouter` hook as the
authorize callback. `op.RegisterLegacyServer` appends a middleware after
the caller options, which chi forbids once a route has been registered;
`op.NewIssuerInterceptor` reproduces the issuer middleware it would
otherwise add. The `/oauth/v2` routing prefix is unchanged.
# Additional Changes
- **`application_type` inference for custom-scheme redirects** (last
commit, isolated). `application_type` is an OpenID Connect member; RFC
7591 does not define it, so clients that only implement RFC 7591 omit
it. Native MCP hosts doing so register custom-scheme redirect URIs,
which ZITADEL reserves for native applications, and the OIDC default of
`web` rejected them with `invalid_redirect_uri`. Inference is limited to
custom schemes and to an absent `application_type`, so every request
accepted today keeps its current application type and auth method — an
`http` loopback redirect still yields a web application. Happy to drop
this commit if you would rather keep it out of this PR.
- **Docs**: a Dynamic Client Registration integration guide, a
`registration_endpoint` section on the OpenID Connect endpoints page,
and the new role in the administrators table (plus its description in
the Console translations).
# Additional Context
- Part of #9810. This is **Phase 1 (DCR)**; #12315 adds RFC 7592 and
#12316 adds CIMD, both stacked on this branch and rebased once this
lands.
- Implements the control-plane and authorization model requested in the
review of this PR, itself a refinement of the direction in
https://github.com/zitadel/zitadel/issues/9810#issuecomment-4891176997.
- **Out of scope (deliberately)**: RFC 7592, CIMD, persisting free-form
metadata (`logo_uri`, `contacts`, and similar), and `private_key_jwt` /
`jwks_uri` (rejected cleanly). No new event types.
- Follow-ups explicitly left out, per the review: DCR client lifecycle
and richer audit, consent and custom scopes, JWT / `aud` (RFC 8707), and
RFC 8414 AS metadata (currently absent from both ZITADEL and
`zitadel/oidc`).
Link the YouTube walkthrough at the end of the quickstart page for users
who prefer watching over reading.
Co-authored-by: Rajat Singh <[email protected]>
# Which Problems Are Solved
- The **Harden secrets** section of the Docker Compose guide lists
commands for the masterkey and the two database passwords, but never
mentions the **initial admin user password**.
- Setting `ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD` to a value that
does not meet the default password complexity policy fails the initial
setup with a `_password_complexity_model` error, and the requirements
were not documented anywhere near the other secrets.
# How the Problems Are Solved
- Adds a short note directly below the secret-generation commands
documenting the initial admin password: the default (`Password1!` /
`[email protected]`), how to override it via
`ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD`, and the default complexity
requirements (min 8 chars, upper + lower + number + symbol).
- Links to the [Password
Complexity](/guides/manage/console/default-settings#password-complexity)
policy docs and notes that the variable only applies during initial
setup.
# Additional Changes
None.
# Additional Context
- Reported by a self-hosting user (TrueNAS + Ansible) who hit repeated
setup errors because the initial admin password complexity requirement
was not documented alongside the masterkey/DB password commands.
## Summary
- Add a new guide documenting how to let users delete their own account
via the User v2 API's `DeleteUser` endpoint, gated by the
`user.self.delete` permission (granted via `ORG_USER_SELF_MANAGER`)
- Link the new guide from the self-service concepts page and sidebar
- Add the `ORG_USER_SELF_MANAGER` role to the administrators reference
table
## Test plan
- [x] Verified the new and updated pages render correctly in local dev
(`pnpm dev`)
replaces #12455 and #12457 (branch got renamed from
`docs/human-pat-clarification`, which auto-closed #12457).
Co-authored-by: Rajat Singh <[email protected]>
# Which Problems Are Solved
In Zitadel's append-only event-sourced architecture, the
`eventstore.events2` table grows indefinitely. PostgreSQL's default
autovacuum uses a percentage-based scale factor, so as the table grows,
the number of changed rows required to trigger a `VACUUM` or `ANALYZE`
drifts towards infinity. Without regular vacuums, the table's Visibility
Map becomes stale, disabling fast Index-Only Scans and forcing expensive
heap reads. Without regular analyzes, query planner statistics become
stale, leading to suboptimal execution plans.
This causes eventstore operations to progressively degrade as `events2`
grows, even without CPU, memory, or I/O saturation. A manual `VACUUM
ANALYZE` immediately restores performance, confirming the root cause.
- If `Eventstore.Autovacuum` is left at its default, `events2` keeps
using PostgreSQL's default, percentage-based autovacuum/autoanalyze
scale factors, which become impractically infrequent on large tables.
- There was previously no supported way to apply static, table-level
autovacuum tuning to `events2` through Zitadel's own configuration/setup
process.
# How the Problems Are Solved
- Added an `Eventstore.Autovacuum` runtime configuration block to
`cmd/defaults.yaml` (disabled by default):
```yaml
Eventstore:
Autovacuum:
Enabled: false # ZITADEL_EVENTSTORE_AUTOVACUUM_ENABLED
VacuumThreshold: 50000 # ZITADEL_EVENTSTORE_AUTOVACUUM_VACUUMTHRESHOLD
AnalyzeThreshold: 50000 # ZITADEL_EVENTSTORE_AUTOVACUUM_ANALYZETHRESHOLD
```
- Added a repeatable `zitadel setup` migration step
(`cmd/setup/eventstore_autovacuum.go`) that:
- When `Enabled: true`, disables the percentage-based
`autovacuum_vacuum_scale_factor`, `autovacuum_analyze_scale_factor`, and
`autovacuum_vacuum_insert_scale_factor` on `eventstore.events2`, and
applies static thresholds (`autovacuum_vacuum_insert_threshold`,
`autovacuum_vacuum_threshold`, `autovacuum_analyze_threshold`) from the
config instead.
- When `Enabled: false`, resets those storage parameters on
`eventstore.events2` back to the cluster defaults.
- Implements `Repeatable.Check()` so the step only re-runs when the
configuration actually changed since the last `zitadel setup` run.
- Added documentation in the new "Performance tuning" page.
# Additional Changes
- Move the projection documentation into performance tuning page
- Expand and update the projection documentation to the latest state in
zitadel. (contained some stale information)
# Additional Context
Several other issues describe read-performance symptoms consistent with
this same root cause (stale `events2` visibility map / planner
statistics at scale, without resource saturation). Since this PR
addresses the shared root cause:
- Closes#12448
- Closes#10754
- Closes#10260
- Closes#8585
- Closes#9239
---
_Generated by [Claude
Code](https://claude.ai/code/session_01HoKvEY7niCVgLCz7CajBwW)_
---------
Co-authored-by: Claude <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Silvan <[email protected]>
Closes#11369
# Which Problems Are Solved
During SSO login with an external IdP, Actions v2 lets you manipulate
the `RetrieveIdentityProviderIntent` response to shape the user that
ZITADEL creates or updates. There was an inconsistency between the two
flows:
- **First login (user does not exist):** the response carried an
`addHumanUser` object (mirroring the deprecated `AddHumanUser` API),
which **does** allow setting user `metadata`.
- **Subsequent logins (user exists):** the response carried an
`updateHumanUser` object (mirroring the deprecated `UpdateHumanUser`
API), which does **not** support metadata.
As a result, actions could set metadata when creating a user but not
when updating one. Customers doing SSO attribute mapping had to make a
separate `SetUserMetadata` call on every subsequent login — extra
latency and a non-atomic update. The proto/backend already gained a
non-deprecated `user_action` oneof (`create_user` → `CreateUserRequest`,
`update_user` → `UpdateUserRequest`, both supporting metadata), but the
login app was still reading the deprecated flat fields, so the new
capability was unreachable from the frontend.
# How the Problems Are Solved
Migrate the login app's IDP intent handler to consume the new
`user_action` oneof, with a fallback to the deprecated fields so older
API responses keep working during the transition.
- **`zitadel.ts`** — added `createUser` / `updateUser` client wrappers
calling the non-deprecated `UserService.CreateUser` /
`UserService.UpdateUser` endpoints.
- **`idp-intent.ts`** — added three helpers, each preferring
`user_action` and falling back to `add_human_user` /
`update_human_user`:
- `resolveCreateUser` — flat read view for org resolution,
required-field checks, and registration-form pre-fill.
- `buildCreateUserRequest` — passes the action's `CreateUserRequest`
through and injects the resolved `organizationId`; maps the deprecated
flat payload into the nested shape on fallback.
- `buildUpdateUserRequest` — builds an `UpdateUserRequest` **including
metadata** (the fix); deliberately syncs only
profile/email/phone/metadata (not username) to preserve existing
auto-update behavior and avoid invalidating sessions.
- Rewired all handlers (`handleUserExists`, `handleAutoLinking`,
`handleAutoCreation`, `handleManualCreation`,
`resolveOrganizationForUser`) to use these, and switched auto-create to
read `CreateUserResponse.id`.
- **Tests** — updated mocks/assertions to the new request shapes and
added two cases exercising the `user_action` oneof with metadata (create
+ update). 817/817 login unit tests pass; no new type errors.
# Additional Changes
Updated the Actions v2 guide
`guides/integrate/actions/testing-response-manipulation.mdx` (the
unreleased/`latest` docs) to reflect the new response shape:
- Go handler example now manipulates `resp.GetCreateUser()` /
`resp.GetUpdateUser()` and appends `user.Metadata`, demonstrating
metadata on both flows.
- Both JSON payloads switched from `addHumanUser` to the nested
`createUser` shape (`human.profile`, `human.email`, `human.idpLinks`,
top-level `metadata`).
- Added a Callout explaining first-login → `createUser` vs.
existing-user → `updateUser`, that both support metadata, and that
`addHumanUser`/`updateHumanUser` are deprecated.
- Updated the claim-mapping debugging section to the new
`createUser.human.profile.givenName` path.
Versioned snapshots (`v4.12`/`v4.13`/`v4.14`) were intentionally left
unchanged, as they document releases where the old API was correct.
---------
Co-authored-by: gayathri <[email protected]>
<!--
Please inform yourself about the contribution guidelines on submitting a
PR here:
https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr.
Take note of how PR/commit titles should be written and replace the
template texts in the sections below. Don't remove any of the sections.
It is important that the commit history clearly shows what is changed
and why.
Important: By submitting a contribution you agree to the terms from our
Licensing Policy as described here:
https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions.
-->
# Which Problems Are Solved
- Operators upgrading from ZITADEL v3 to v4 lacked a clear, docs-backed
upgrade path covering web keys (A-10017), Login V1 vs Login V2
expectations, and post-upgrade options.
- There was no standalone guide for adopting Login V2 after already
running on v4.
- Related ops/docs pages did not consistently point readers to the
upgrade and Login V2 adoption material.
- Kubernetes/Helm operators upgrading to a chart that ships ZITADEL v4
had no ops-page section linking the advisory, upgrade guide, optional
Login V2 deferral (`login.enabled: false`), and chart README upgrade
notes.
# How the Problems Are Solved
- Adds technical advisory **A-10017** documenting OIDC web key staging
requirements before upgrading to v4.
- Adds an **upgrade-v3-to-v4** guide with the recommended upgrade path
(Login V1 remains supported; Login V2 is optional at upgrade time).
- Adds a **v4-only adopt-login-v2** guide for teams adopting Login V2
later, with steps and caveats separate from the version upgrade.
- Adds an **Upgrading to ZITADEL v4** subsection under
`self-hosting/deploy/kubernetes/operations.mdx` that links to A-10017,
the upgrade guide, Adopt Login V2, notes `login.enabled: false` when not
adopting Login V2 yet, and points to the [Helm chart
README](https://github.com/zitadel/zitadel-charts/blob/main/charts/zitadel/README.md)
for chart-specific breaking changes.
# Additional Changes
- Registers new docs in the sidebar and wires Related / cross-links from
upgrade and ops pages so the advisory and guides are discoverable
together.
- Aligns language so upgrade vs. Login V2 adoption are clearly separated
concerns.
- Light cross-link updates on troubleshooting / updating-scaling /
login-client pages where related.
# Additional Context
- Related: A-10017 (web keys advisory)
- Docs paths introduced/updated:
- `apps/docs/content/support/advisory/a10017.mdx`
- `apps/docs/content/self-hosting/manage/upgrade-v3-to-v4.mdx`
- `apps/docs/content/self-hosting/manage/adopt-login-v2.mdx`
- `apps/docs/content/self-hosting/deploy/kubernetes/operations.mdx` (new
"Upgrading to ZITADEL v4" section)
- Sidebar and Related / cross-links
- Companion Helm chart README PR:
https://github.com/zitadel/zitadel-charts/pull/608
---------
Co-authored-by: Cursor <[email protected]>
## Summary
- V2 has already reached end of support; the roadmap page previously
implied end-of-support timelines for V2 and V3 were both still
forthcoming.
- Updated copy to state V2's end-of-support status plainly and note that
V3's timeline and migration guidance will be published soon.
## Test plan
- [x] Verified the updated sentence renders correctly in
`apps/docs/content/product/roadmap.mdx`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Elina Sokolovska <[email protected]>
Co-authored-by: Claude Sonnet 5 <[email protected]>
## Which Problems Are Solved
There is currently a knowledge gap in the documentation where users are
unclear on if or why human accounts cannot have Personal Access Tokens
(PATs), which has led to confusion in community channels like Discord
and GitHub.
## How the Problems Are Solved
Clarified that human PATs are currently not supported in the Personal
Access Token documentation and added a direct link to GitHub Issue
#10915 so users can track and upvote this capability.
## Additional Changes
None
## Additional Context
None
---------
Signed-off-by: Rajat Singh <[email protected]>
Co-authored-by: Rajat Singh <[email protected]>
## Which Problems Are Solved
The callout added in #12335 included a link to a GitHub discussion which
will become outdated and difficult to maintain.
## How the Problems Are Solved
Removes the discussion link from the ZITADEL Cloud bullet point in the
TOTP issuer callout, keeping the rest of the callout intact.
Co-authored-by: Rajat Singh <[email protected]>
# Which Problems Are Solved
- No documentation explained that the TOTP issuer name defaults to
"ZITADEL" and is not derived from the domain
- No documentation clarified how to change it on self-hosted (env var
only, Helm values don't work)
- No documentation communicated that it is not configurable on ZITADEL
Cloud
# How the Problems Are Solved
- Adds a callout in the MFA section of the default settings page
covering both self-hosted
(ZITADEL_SYSTEMDEFAULTS_MULTIFACTORS_OTP_ISSUER) and cloud (not
configurable, discussion link)
# Additional Changes
None
# Additional Context
- https://github.com/zitadel/zitadel/discussions/5453
Signed-off-by: Rajat Singh <[email protected]>
Co-authored-by: Rajat Singh <[email protected]>
## Summary
- Update the description frontmatter and reword "next generation" to
"next iteration" throughout
- Add a "What this means for existing customers" subsection after the
disclaimer, before the Strategic Roadmap section
- Convert `### **Strategic Investments**` / `### **Customer Outcomes**`
subheadings to bold text so they no longer appear in the TOC
- Rename the closing section to "Migration and Adoption" with updated
content
## Test plan
- [x] Diffed against source content to confirm all requested sections
match
- [x] Verified no remaining "next generation" occurrences
---------
Co-authored-by: Elina Sokolovska <[email protected]>
Co-authored-by: Florian Forster <[email protected]>
Automatically generated PR resolving Knowledge Gap ID 34.
**Thread ID:** manual-1783339162398
**Action:** CREATE
**New File:** `content/docs/drafts/gap-manual-1783339162398.mdx`
### AI Summary
> Add a user migration guide for firebase to Zitadel
### Human Reviewer Instructions
> Firebase to Zitadel Migration Summary
> The Blocker: Incompatible Password Hashes
>
> Firebase uses a proprietary, modified scrypt algorithm requiring
project-specific keys.
>
> Zitadel supports standard algorithms but lacks a verifier for
Firebase's custom format.
>
> Result: Passwords cannot be directly imported. Firebase hashes must be
discarded.
>
> Strategy 1: Bulk Import & Password Reset (Standard)
> Requires users to set a new password on their first login.
>
> Export: Run firebase auth:export users.json --format=json.
>
> Map: Convert Firebase fields (e.g., localId) to Zitadel's schema.
>
> Import to Zitadel: Call the Zitadel import API.
>
> Action: Omit the hashedPassword object entirely.
>
> Action: Include "passwordChangeRequired": true in the JSON payload to
trigger a reset flow via email or login prompt.
>
> Strategy 2: Just-In-Time (JIT) Migration (Seamless)
> Migrates users transparently behind the scenes during an active grace
period.
>
> Intercept Login: Your backend captures the plain-text password during
login.
>
> Verify: Backend POSTs credentials to Firebase Auth REST API:
>
>
https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=[WEB_API_KEY]
>
> Migrate to Zitadel:
>
> If Valid (200 OK): Create the user in Zitadel immediately using the
plain-text password (Zitadel will natively hash it).
>
> If Invalid (400): Reject login or check if the user is already in
Zitadel.
>
> Sunset: After the grace period ends, migrate remaining inactive users
using Strategy 1.
> (Note: Do not log plain-text passwords and strictly enforce HTTPS
during this phase).
---
🤖 **Need adjustments?**
Leave a comment below and tag **@zitadel-knowledge-bot** with your
requested changes, and I will automatically update the files and push a
new commit!
---------
Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Removes the release cycle page and sidebar entry. Replaces the roadmap
page with updated strategic roadmap content and renames its sidebar
label to Roadmap.
---------
Co-authored-by: Elina Sokolovska <[email protected]>
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Fabienne Bühler <[email protected]>
# Which Problems Are Solved
- The documentation had no guidance for debugging auto-user-creation
failures caused by unexpected or missing claims from an external IdP,
leaving users without a path forward when they see a
`SetHumanProfile.GivenName` validation error during OIDC login.
# How the Problems Are Solved
- Adds a troubleshooting section to the Actions V2 response manipulation
guide explaining how to use the `RetrieveIdentityProviderIntent` webhook
to inspect `rawInformation` and identify claim key mismatches from the
external IdP.
# Additional Changes
- None
Signed-off-by: Rajat Singh <[email protected]>
Co-authored-by: Rajat Singh <[email protected]>
# Which Problems Are Solved
Zitadel exposes the secrets generator configuration through its admin
api. This allows instance admins to manage them on their own and they
can create overwrite the system / runtime defaults (incl. expiration).
This very much needed in multi-instance scenarios such as zitadel.cloud.
Currently the invite code configuration was not manageable through the
API, but only runtime config.
# How the Problems Are Solved
- added the `invite_code` type to the API allowing it to be set and
retrieved.
- added the type to console's management list
- added the type to be stored on instance setup
- change the `GetSecretGenerator` endpoint to fall back to the runtime
config if no config is stored on the instance itself
- ensure the `length` and at least one charset is enabled, return an
error otherwise
- expiry is not enforced, so 0 allows codes with no expiry (current
state)
# Additional Changes
None
# Additional Context
- closes https://github.com/zitadel/zitadel/issues/10474
Automatically generated PR targeting 1 files.
**Thread ID:** 1517531035175354430
**Action:** UPDATE
**AI Summary:**
> Documentation does not explain what happens when users bookmark the
login page or access ZITADEL without an OIDC flow, particularly
regarding redirect behavior and the purpose of organization Default
Redirect URI settings.
Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>
Automatically generated PR targeting 1 files.
**Thread ID:** 1512031509387673610
**Action:** UPDATE
**AI Summary:**
> The documentation lacks clear explanation of how the 'Use new login
UI' checkbox and 'Custom base URL for the new Login UI' field work
together, including step-by-step configuration and troubleshooting
guidance.
---------
Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>
Co-authored-by: fcoppede <[email protected]>
Automatically generated PR targeting 1 files.
**Thread ID:** manual-1782237446658
**Action:** UPDATE
**AI Summary:**
> Need to add a note on this page to encourage users with active
subscriptions to link their github and Discord account because that will
help github issues get higher priority and discord threads as well
Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>
<!--
Please inform yourself about the contribution guidelines on submitting a
PR here:
https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr.
Take note of how PR/commit titles should be written and replace the
template texts in the sections below. Don't remove any of the sections.
It is important that the commit history clearly shows what is changed
and why.
Important: By submitting a contribution you agree to the terms from our
Licensing Policy as described here:
https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions.
-->
# Which Problems Are Solved
The projects setting texts and sceenshot where outdated.
# How the Problems Are Solved
Update the text and screenshot.
# Additional Changes
* Rewording
* Lockout warning component
---------
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
# Which Problems Are Solved
Pages in `apps/docs` that embed source from GitHub via the Docusaurus
convention
````
```js reference
https://github.com/zitadel/actions/blob/main/examples/org_metadata_claim.js
```
````
stopped working after the migration from Docusaurus to fumadocs. The old
`docusaurus-theme-github-codeblock` plugin used to fetch the file and
render it; fumadocs has no support for that meta, so the page rendered
the raw URL as plain code-block text. Visible at
`/docs/apis/actions/code-examples` and 16 other pages.
# How the Problems Are Solved
- Converted every ```` ```<lang> reference\n<URL>\n``` ```` block (46
total across 17 `.mdx` files) to the native fumadocs JSX form:
`<GithubCodeBlock url="<URL>" />`. The existing `<details>`/`<summary>`
collapsibles around blocks are kept — they're an authoring choice, not
part of the rendering bug.
- Updated `apps/docs/components/github-code-block.tsx` to render via
`DynamicCodeBlock` from `fumadocs-ui/components/dynamic-codeblock`
(proper shiki highlighting) instead of raw `CodeBlock` + `Pre` (which
produced unhighlighted output). Also fixed language detection so a URL
hash like `#L10-L20` no longer pollutes the language token.
- Registered `GithubCodeBlock` globally in
`apps/docs/mdx-components.tsx`, matching how every other shared
component (`APIPage`, `Callout`, `Tab/Tabs`, `Step/Steps`, `Admonition`,
`TerminologyUpdate`) is exposed. MDX files no longer need a local
`import`.
# Additional Changes
- Normalized the two MDX files that were already using the JSX form
(`examples/secure-api/python-django.mdx`,
`examples/secure-api/java-spring.mdx`): removed their now-redundant
local `import { GithubCodeBlock }` and rewrote 9 long-form
`<GithubCodeBlock url="..."></GithubCodeBlock>` tags to self-closing for
consistency.
# Additional Context
Verified locally with `pnpm --filter @zitadel/docs dev`:
- `/docs/apis/actions/code-examples` — 20 shiki-highlighted code blocks
rendered inside the `<details>` collapsibles (was 0).
- `/docs/apis/openidoauth/claims` — line-range hashes (`#L9-L11`)
honored.
- `/docs/examples/login/flutter` — mixed languages (xml/dart/html)
detected and highlighted.
- `/docs/guides/integrate/external-audit-log` — edge case of fenced
reference indented inside a numbered list also converted and rendered.
Greps:
- `^[ \t]*\`\`\`[a-zA-Z0-9]+ reference` in `apps/docs/content/**/*.mdx`
→ 0 matches.
- `<GithubCodeBlock url="` in `apps/docs/content/**/*.mdx` → 55 matches.
- `from '@/components/github-code-block'` in
`apps/docs/content/**/*.mdx` → 0 matches.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
## Description
This PR updates the Kubernetes deployment documentation to address user
feedback regarding our routing instructions. A user noted that standard
`Ingress` is becoming outdated and requested configuration examples for
the modern Gateway API.
To support both existing and modern clusters, I have updated the guide
to provide two clear pathways for exposing ZITADEL.
## Changes Included
* **Prerequisites updated:** Mentioned Gateway API controllers alongside
standard Ingress controllers.
* **Refactored Stage 2 (Production):** Split the routing configuration
into "Option A: Standard Ingress" and "Option B: Gateway API".
* **Added YAML example:** Provided a sample `HTTPRoute` resource to
route traffic to the `zitadel` and `zitadel-login` backend services.
* **Terminology updates:** Broadened terms like "Ingress" to "Routing"
or "Routing controller" where applicable.
This PR updates the "Private Key JWT Auth for Service Accounts"
documentation to explicitly clarify the relationship between the exp
(expiration) and iat (issued at) claims.
Previously, the documentation didn't make it clear what happens if a
developer sets an exp claim far into the future. This update clarifies
that while the exp value is strictly enforced, the iat claim takes
precedence if the exp is set to more than 1 hour in the future (i.e.,
ZITADEL will reject the JWT once the iat is older than 1 hour,
regardless of the exp time).
**Changes included:**
Updated the description of the exp claim in the JWT payload section to
highlight the 1-hour iat limit enforcement.
# Which Problems Are Solved
As part of https://github.com/zitadel/zitadel/issues/11917 we want to
introduce error slugs so (API) clients can rely on stable,
machine-readable errors and act accordingly.
# How the Problems Are Solved
- Added a `NewSlug` helper function in the domain package.
- Added `ErrorDetails` to the `ZitadelError`
- Added an `zitadel.error.v2.ErrorDetail` proto message
- Updated the connectRPC error interceptor to map new slug based errors
to the new `ErrorDetail`
- Defined some common slugs and error functions like internal errors
- Defined (session) specific slugs used in the `DeleteSession` and
`CheckUser` functions and replaced old implementations
- Updated integration tests to check specific errors if the relation
database feature is enabled
- Updated doc and guideline to reflect the latest changes and decisions
- Updated DeleteSession endpoint API to list possible slugs
# Additional Changes
None
# Additional Context
- closes#11957
---------
Co-authored-by: Wim Van Laer <[email protected]>
# Which Problems Are Solved
The documentation did not reflect the difference between login v1 and v2
for configuring a JWT-idp
# How the Problems Are Solved
Added a warning in the docs which highlight that difference.
# Additional Changes
- Restructured the docs to reflect a more generic approach.
- Fixed indentation inside the `<Callout type="warning">` block so the
content renders as formatted text instead of a code block.
- Fixed inconsistent acronym casing in the use-case section: "jwt" →
"JWT" and "idp" → "IdP" to match the rest of the page and UI terms.
# Additional Context
- Closes: #11589
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: Copilot <[email protected]>
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
## Problem statement
- `<Tabs>` were not rendered in docs
- Missing steps in README for local dev
## How it was solved
- Enhance the README to better describe local development setup and
clean up formatting.
- Standardize the use of tabs across various components for consistency.
## Additional info
To not cause merge conflicts with
https://github.com/zitadel/zitadel/pull/11729 changes in
`/self-hosting/manage/configure/configure.mdx` were skipped
# Which Problems Are Solved
System API users currently authenticate using raw RSA public keys
configured via Path or KeyData. This approach doesn't integrate well
with Kubernetes tooling.
# How the Problems Are Solved
Allow for the `path`/`keyData` to be an X.509 certificate.
The `NotBefore` and `NotAfter` fields of the certificate are beeing
respected when validating the JWT.
# Additional Changes
# Additional Context
- Closes#11442
---------
Co-authored-by: Livio Spring <[email protected]>
Added a callout to the Kubernetes deployment guide (`Deploy ZITADEL on
Kubernetes`) explicitly stating that PostgreSQL 18 is not currently
supported, ensuring users deploy with a compatible version (14-17).
This PR updates the "Hosted Login UI" documentation to clarify how text
customization works for the new Hosted Login V2.
**Changes:**
1. Adds a warning callout to the "Customization options" section.
2. Clarifies that Login V2 interface texts currently cannot be modified
via the ZITADEL Console UI.
3. Provides the required workaround, directing users to use the Settings
V2 API to patch translation keys using the en.json locale file as a
reference.
---------
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
# Which Problems Are Solved
For existing customers it could be hard to understand the new aligned
terms we have defined, if they are already used to the "old term"
# How the Problems Are Solved
Adding notes at the top of docs pages to state clearliy new and old
terms.
# Which Problems Are Solved
- Apple Sign-In uses a Form Post method to return the authorization
response, which ZITADEL handles at a specific endpoint ending in /form
for the login v1.
- The Console previously displayed the standard callback URL (without
`/form`), causing confusion and configuration errors..
# How the Problems Are Solved
- **Console**: Updated ProviderNextService to accept a suffix string .
- **Console**: Updated ProviderAppleComponent to append the suffix to
the urls.
# Additional Changes
- Updated `docs/docs/guides/integrate/identity-providers/apple.mdx` to
explicitly state that the callback URL end with `/form`
- Updated `docs/docs/guides/integrate/login-ui/external-login.mdx` to
clarify the `/form` suffix requirement for Apple Sign-In in the hosted
login documentation.
# Additional Context
- fixes#11438
---------
Co-authored-by: Ramon <[email protected]>
Co-authored-by: Florian Forster <[email protected]>
# Which Problems Are Solved
The distinction between Example Applications and SDKs has not been
clear. In the SDKs section a lot of Example applications where listed.
Also In the SDKs it was not really clear what the SDKs actually are.
Also some examples and skds are outdated and not well maintained
anymore.
# How the Problems Are Solved
- Moved all example app guides to the Example Applications Section
- Restructured SDKs Nav --> SDKs & Integrations: Frontend & Mobile,
Backend & API, Management API Clients
- Removed unnecessary / outdated guides
- Remove ids in framework.json for frameworks which shouldn't be
rendered in console
## Todos:
- Update SDKs Introduction page
- Add links to common oidc libs for most used frameworks
---------
Co-authored-by: Mridang Agarwalla <[email protected]>
<!--
Please inform yourself about the contribution guidelines on submitting a
PR here:
https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr.
Take note of how PR/commit titles should be written and replace the
template texts in the sections below. Do not remove any of the sections.
-->
# Which Problems Are Solved
The `/apis/introduction` page did not give developers clear guidance on
which API version to use. v1 and v2 were presented as equal choices, the
v2 resource list was incomplete, the API path prefix block was
misleading, and several links used inconsistent relative paths.
- No clear recommendation to use v2 for new integrations
- v1 APIs not labelled as legacy
- "APIs v2" section only listed 3 of 15 available v2 services
- Path prefix block only listed 5 of 15 v2 gRPC service paths, with no
distinction between REST and gRPC/Connect access patterns
- `/ui/` path listed as a single entry — the Management Console
(`/ui/console/`) and hosted Login UI (`/ui/login/`) were not
distinguished
- Relative links (`../guides/...`, `./assets/assets`) inconsistent with
the rest of the page
- Auth disclaimer paragraph was confusing; "Custom" section heading was
unclear
# How the Problems Are Solved
- **v2-first framing**: Added a clear directive — "Use the v2 APIs for
all new integrations" — and renamed the old section to "Legacy v1 APIs"
- **Complete v2 resource list**: All 15 v2 services now listed with
one-line descriptions (User, Session, Org, Instance, Project,
Application, IDP, Group, Settings, Feature, Authorization, Action,
WebKey, OIDC, SAML)
- **Full path prefix list**: All 15 v2 gRPC service paths listed; split
into `# REST (HTTP/JSON transcoding): /v2/` and `# gRPC + Connect
protocol (binary or JSON via connectRPC):`
- **`/ui/` paths clarified**: Split into `/ui/console/` (Management
Console) and `/ui/login/` (Hosted Login UI) with inline comments
- **Fixed relative links**: `../guides/...` → `/guides/...`;
`./assets/assets` → `/apis/assets/assets`
- **Section copy improvements**: Auth disclaimer removed; "Custom"
renamed to "Session-based and custom login" with clear bullet list; v1
API card descriptions updated with v2 pointers; Assets card, System card
title fix
- **Client libraries section**: Replaced noisy "API definitions" section
with cleaner "Client libraries & schemas"
# Additional Context
All changes are in `apps/docs/content/apis/introduction.mdx`,
`apps/docs/content/apis/v2.mdx`, and
`apps/docs/content/apis/migration_v1_to_v2.mdx`.
Co-authored-by: Copilot <[email protected]>
## What
Shorten the `title` frontmatter on 20 docs pages so that the rendered
`<title>` tag stays within the 70-character SEO limit.
The root layout appends ` | ZITADEL Docs` (15 chars) to every page
title, meaning the raw frontmatter `title` must be ≤ 55 characters. A
site scan flagged all 20 pages for the "Title too long" warning (titles
ranged from 71 to 92 characters in the final rendered tag).
## Why
Search engines may truncate or ignore `<title>` tags longer than 70
characters, hurting click-through rates and keyword relevance in search
results.
## How
Each title was rewritten to:
- Fit within 55 characters (leaving room for ` | ZITADEL Docs`)
- Front-load the primary search keyword (product name, protocol, or
feature)
- Include "ZITADEL" where users realistically search for `{feature}
ZITADEL`
- Avoid generic filler words ("Configure", "Set up", "Support for")
All 20 files already have a `sidebar_label` field that preserves the
original descriptive title for sidebar navigation — no navigation
changes result from this PR.
## Pages updated
| Page | Before (chars) | After (chars) |
|---|---|---|
| concepts/features/passkeys | 82 | 64 |
| guides/integrate/identity-providers/openldap | 72 | 58 |
| guides/solution-scenarios/b2b | 78 | 59 |
| guides/integrate/services/cloudflare-oidc | 86 | 54 |
| guides/integrate/login/hosted-login | 71 | 55 |
| guides/integrate/service-accounts/private-key-jwt | 76 | 56 |
| guides/integrate/service-accounts/client-credentials | 78 | 60 |
| guides/integrate/services/google-cloud | 92 | 57 |
| guides/solution-scenarios/saas | 88 | 65 |
| guides/migrate/sources/auth0 | 73 | 65 |
| guides/integrate/login-ui/oidc-standard | 81 | 55 |
| guides/integrate/login-ui/username-password | 73 | 59 |
| guides/integrate/login-ui/device-auth | 78 | 60 |
| guides/integrate/identity-providers/linkedin_oauth | 72 | 58 |
| guides/integrate/identity-providers/pingfederate-saml | 74 | 61 |
| guides/integrate/service-accounts/personal-access-token | 82 | 62 |
| legal/service-description/cloud-service-description | 76 | 61 |
| guides/integrate/login/login-users | 83 | 55 |
| guides/integrate/identity-providers/migrate | 74 | 65 |
| guides/manage/console/projects-overview | 71 | 62 |
All final `<title>` values are between 54 and 66 characters (well under
the 70-char limit).
# Which Problems Are Solved
Renaming of:
- Resource Owner
- OrgID
- OrganizationID
- Organization Id
# How the Problems Are Solved
Find & Replace, evalue entries, is resource owner referring to an
organization ? If so, do change
# Additional Context
- Closes#11305
Users deploying ZITADEL against a managed PostgreSQL service (RDS, Cloud
SQL, Azure Database, etc.) often do not have superuser access and cannot
provide `Admin.*` credentials. The documented workaround — provisioning
the user and database manually and then running `start-from-setup` —
silently skips schema bootstrapping, causing `relation
"eventstore.events" does not exist` errors with no clear recovery path.
The root cause is that `zitadel init` conflates two steps that require
different privileges without exposing them separately:
- **Provisioning step** (`CREATE ROLE`, `CREATE DATABASE`, `GRANT`) —
requires superuser.
- **Schema bootstrapping step** (create
`eventstore`/`projections`/`system` schemas and base tables) — requires
only DB owner.
Users who handle the provisioning step externally have no supported way
to run schema bootstrapping alone.
## Changes
- **`cmd/initialise/verify_schema.go`** (renamed from
`verify_zitadel.go`): Rename `newZitadel()` → `newSchema()` (internal);
rename the `init zitadel` sub-command to `zitadel init schema`
(backwards-compatible alias kept) with a clear description that it
bootstraps the ZITADEL database schema without admin/superuser
privileges. Fix stale error log message to reference `init schema`.
- **`cmd/initialise/verify_schema_test.go`** (renamed from
`verify_zitadel_test.go`): Test file renamed to match source file.
- **`cmd/initialise/init.go`**: Update call site to `newSchema()`. Add
guidance in the `init` command's Long description about using `zitadel
init schema` for users without admin credentials.
- **`cmd/initialise/verify_database.go`**: Add a `pg_database` catalog
pre-check before attempting `CREATE DATABASE`, so `zitadel init` with
`ADMIN=service_user` no longer fails with `permission denied to create
database` when the database was already provisioned externally.
- **`cmd/initialise/verify_database_test.go`**: Add test cases covering
the new catalog-check skip path, the existing error-skip path, and the
error-propagation path when the `pg_database` query itself fails.
- **`apps/docs/content/self-hosting/manage/database/index.mdx`**: Inline
the `_postgres.mdx` partial (now only PostgreSQL is supported), add a
top-level callout, and add a **Managed PostgreSQL / No Admin Access**
section with explicit 3-step instructions and security guidance (strong
passwords, SSL, TLS). Additional improvements: add inline `# Use
'require' or 'verify-full' for production` comments on `Mode: disable`
lines in the YAML example; add clarifying comment to the redundant
`GRANT` in the SQL snippet; replace admonition syntax with proper
Fumadocs `<Callout>` components; add full database connection env vars
to the `start-from-setup` example.
- **`apps/docs/content/self-hosting/manage/updating_scaling.mdx`**:
Rewrite the init phase description to clearly distinguish the
provisioning and schema bootstrapping steps, and document `zitadel init
schema` as the path for manual provisioning. Replace admonition syntax
with proper Fumadocs `<Callout>` components.
- **`apps/docs/content/self-hosting/manage/database/_postgres.mdx`**:
Deleted (content merged into `index.mdx`).
## Problem
This relates to https://github.com/zitadel/zitadel/discussions/9363
I think we can improve our UX in cases where a user wants to use an
external DB and/or does not want to share too broad permissions with
zitadel
## Related problems
* https://github.com/zitadel/zitadel/issues/10432
* https://github.com/zitadel/zitadel/discussions/8583
* https://github.com/zitadel/zitadel/issues/7903
* https://github.com/zitadel/zitadel/issues/9718
* https://github.com/zitadel/zitadel/issues/8012
* https://github.com/zitadel/zitadel/issues/8558
## Related PRs
https://github.com/zitadel/zitadel/pull/11021