Docs

Release notes

What changed in each version of the zitadel CLI, newest first, from its changelog. --version prints the version you run: npx @zitadel/cli@latest --version.

0.3.0

Minor changes

  • zitadel policies test runs the cases in .zitadel/policies/<operation>.test.json against your policy. Each case gives the operation's input (for the password policy, the password itself), optionally the facts the operation would look up (self_service, history_matches), and the decision it expects: allowed, and optionally the error code and exactly the violations. Zitadel evaluates every case as a real password set would, so a case also covers how the password is counted. By default the cases run against the policy file beside them; --release <id> runs them against the policy a built release pins, before you deploy it. A failing case fails the command with E_VALIDATION and lists what was expected and what Zitadel decided. deploy never carries test files, editors validate them against the new .zitadel/meta/policy-test.json, and the policies README that setup writes shows an example. (#2353)

  • The usage analytics events the CLI sends to Vercel Web Analytics now carry one property of the command's own for setup (framework), claim (claim_outcome), doctor (failed_checks), api (operation) and start (runtime). On a failed run it takes the place of non_interactive. The kind of data sent does not change: fixed names only, never a path, id or message. (#2428)

  • zitadel deploy --dry-run --json now reports what the deploy would change, so a script or an agent can review it before deploying: data.diff lists each changed file under .zitadel/ with its status and its part of the diff the terminal shows, against the release your primary origins serve. zitadel deploy --json reports the same diff for what it deployed. (#2288)

  • POST /users/query can find the users still on an older revision of a schema. The new schema_object_type filter field matches the object type every revision of a schema shares, with equals, not_equals and contains, and schema now accepts not_equals, so schema_object_type equals a type together with schema not_equals its newest revision lists the users left to migrate. zitadel users list --filter accepts schema_object_type. (#2129)

  • setup now commits the project it scaffolds, initialising a repository first when there is none, so a release records the commit it was built from and a later deploy can show a real diff. Because the commit covers the project directory, setup refuses a repository that already has uncommitted work, untracked files included; only the .zitadel/local/ and .gitignore that start leaves are exempt. It also points at the deploy that pins .zitadel/ into a release. --no-git opts out of the commit. (#2321)

Patch changes

  • Commands that zitadel setup, start, doctor and env add suggest after a --cwd run now work when pasted into the same shell: --env-file paths, the install and dev-server commands, setup --server local and console name the project directory. A relative --env-file is now read from the current directory, where Node itself reads it, instead of from --cwd. (#2416)

  • zitadel doctor now says when it checks a local server started in a parent folder, in the runtime check and when that server is unhealthy. The unhealthy message names the parent, says that reset --force deletes the parent's local data, and suggests a plain start to give the current folder a server of its own. (#2424)

  • zitadel setup --cwd <dir> creates <dir> when it does not exist yet, instead of failing with a raw ENOENT error, and removes it again if setup does not finish. A --cwd that is a file now fails with a clear message. (#2416)

  • Updated dependencies: @zitadel/server@0.3.0, @zitadel/config@0.3.0, @zitadel/api@0.3.0.

0.2.0

Minor changes

  • The CLI's usage analytics events now carry surface: cli, which names the Zitadel component that sent them, so they can be read beside the events of other Zitadel components. Nothing else they carry changes. (#2292)

  • zitadel claim --no-wait prints the claim link and exits, with claim_url, challenge_id and expires_at under --json, so a script or coding agent can hand the link to a person; zitadel claim --challenge-id <id> then waits for that link to be used and records the claim. (#2402)

  • Running zitadel alone now lists the commands under six headings named for what you are doing, one row per family such as users or release. zitadel users --help lists its operations, every command's help ends by pointing at the overview, and zitadel commands still prints the flat list. (#2348)

  • zitadel auth-method sso enable --provider custom now asks at a terminal for the connection's name, its sign-in button's name, the issuer URL and the scopes you leave out, instead of failing with a list of flags; scripts still need the flags (docs). A new custom connection also prints its claim mappings and the file to edit them in. (#2367)

  • The CLI's onboarding output now says what to do next: zitadel start points to zitadel setup instead of a console link, setup spells out every choice and shows the sign-in configuration before writing it, and auth-method sso enable says to deploy the provider, then test sign-in. The note about Zitadel's agent skills shows once per machine. (#2323)

    Breaking: zitadel start --json no longer has data.console, and its next_actions and next_commands no longer mention the console; a script that reads them gets nothing there. Run zitadel console --json for data.sign_in_url.

  • zitadel origin add <pattern> --kind primary|preview, origin list and origin rm manage the origins a project serves, with the project secret (docs). A primary origin is an exact URL, and a preview wildcard on a shared host needs a literal label of your own. (#1650)

  • zitadel deploy now carries operation policies from .zitadel/policies/, one file per operation, checked against the new .zitadel/meta/policy.json schema before anything is sent; zitadel policies list and policies get read them back (docs). Sign-ins and sign-ups use the policy the deployed release carries, so deploying without a file switches that policy off. (#2270)

  • Preview a branch's release on its pull-request URLs with zitadel preview. Run inside a preview build, it reads the preview URLs from the hosting platform (Vercel or Netlify; pass Cloudflare Pages URLs with --origin) or from --origin, builds the same release deploy does, validates it without touching your live sign-in providers, and makes it live on those URLs for a time-to-live (--ttl, between 60s and 365d; omitted, the project's default applies). Its last line is NEXT_PUBLIC_ZITADEL_RELEASE=sha256:... for the build to pin. Outside a preview or development build it does nothing — even with an explicit --origin — because production is deployed by zitadel deploy after merge, and --origin primary or a primary origin's own URL is refused. On a CI platform other than Vercel or Netlify, set ZITADEL_ENV=preview in the pull-request build: a CI build that names no environment is skipped too. (#1652)

    zitadel preview list shows the project's live previews, and zitadel preview rm <url> ends one early. All three authenticate with ZITADEL_PROJECT_SECRET for now, because the server does not accept the narrower preview token yet (#1575); they never fall back to the other credential. preview list and preview rm fail, naming the variable, when it is not set; zitadel preview instead warns and still exits successfully unless --strict is passed.

  • zitadel pull rebuilds .zitadel/ from what a release pins — the live release by default, a release id (rel_…) or a deployment id (dep_…) — so the next deploy builds the same release (docs). It restores schemas and flow definitions, and warns for branding and identity provider connections, which it cannot read back yet. (#1407)

  • zitadel start no longer reports a login URL, because the server no longer serves a login page: sign in through the login page zitadel setup adds to your app. (#2307)

    Breaking: zitadel start --json no longer has data.urls.login; a script that reads it gets nothing there.

  • The CLI now sends its anonymous usage events to Vercel Web Analytics on the Zitadel cloud (preview.zitadel.com), with at most eight of their properties, and sends nothing to Mixpanel for now. The existing opt-outs (--no-telemetry, DO_NOT_TRACK=1, ZITADEL_TELEMETRY=0) turn them off, and runs in CI send nothing. (#2253)

  • zitadel logs now shows the local server's warnings in yellow and errors in red, and --level warn shows only those, so the reason a sign-in failed is easy to find. zitadel start and zitadel doctor say to keep zitadel logs --follow open, and zitadel logs works from an app inside the directory zitadel start ran in. (#2245)

  • zitadel deploy replaces plan and apply: it builds .zitadel/, identity provider connections included, into one immutable release, shows its git diff against the live one, and makes it live on the project's primary origins (guide). New release and deployment commands, and their @zitadel/api operations, create, list, deploy, roll back and revoke releases directly; a rollback lists the revisions it restores. (#1651)

    Breaking: plan and apply are removed. Use deploy --dry-run where you ran plan, and deploy where you ran apply. The generated releases list and releases get are now release list and release get, beside release create and release revoke. Add the origins your project serves with origin add before the first deploy — deploy needs at least one primary origin. A server refusal because of the state it holds (an HTTP 409) now fails every command with E_CONFLICT (exit 5) rather than E_VALIDATION (exit 3), and a target the project does not hold as an origin fails with E_VALIDATION rather than E_AUTH.

    Upgrading a project set up with 0.1.0: its app URL is registered as a preview origin only, so its first deploy fails with "The project has no primary origin to deploy to". Run zitadel doctor --fix once to make that URL primary, as zitadel setup now does; zitadel doctor warns about such a project, and deploy suggests the fix. To do it by hand, find the URL with zitadel origin list, remove it with zitadel origin rm <url>, add it back with zitadel origin add <url> --kind primary, then deploy. Adding it again without removing it fails, because the URL is already stored as a preview origin.

  • Commands that talk to a project now resolve its server, project and credential per environment, so one checkout can serve development, staging and production; zitadel env shows what resolved and from where, env list lists the environments and env add binds one (docs). Values come from flags, the process environment, .env.<name>.local, .env.local, .env.<name> and .env; only development reads .env.local. (#1649)

    Breaking: the server no longer comes from ZITADEL_API_BASE, the server field of zitadel.json, or a cloud default. Set ZITADEL_URL in an env file, or pass --server; only setup still defaults to Zitadel Cloud. zitadel.json no longer gets an environments block, environments list and environments get are gone, and -e now means --env.

  • Variables now belong to the project, under a new variable group, and hold a primary value, which primary deployments freeze, and a preview value, which previews freeze, with no fallback between them (docs). A plain variable set writes both, a secret only the primary, and --preview works with the preview value alone; variable resolve shows what a live deployment runs.

    Breaking: the variables commands and the --project-level flag are removed. Drop --project-level, and use variable set NAME, variable get NAME, variable list, variable rm NAME in place of the old variables set/get/list/delete.

  • zitadel setup now writes and publishes a password policy for every new project, .zitadel/policies/auth.factor.password.set.json, with a minimum of 15 characters, NIST's minimum without a second factor, and a README on its settings. The wizard offers 15, 8 or none; --password-min-length <n> sets another, 0 none. Change it in the file and publish it with zitadel deploy. (#2279)

    Breaking: a scripted zitadel setup now publishes a 15-character minimum. If your scripts or tests register users with passwords shorter than 15 characters, use longer ones, or pass --password-min-length with the length you need.

  • The src/hooks.server.ts that zitadel setup --framework sveltekit writes now reads ZITADEL_URL, ZITADEL_PROJECT_SECRET and ZITADEL_PROJECT_ID from SvelteKit's $app/env/private, declared in src/env.ts, so npm run check passes without @types/node, and a missing setting fails with an error naming it instead of proxying to a default server. (#2383)

    Breaking: setup refuses SvelteKit 2 apps, which have no $app/env/private, and zitadel doctor and zitadel eject no longer recognize a SvelteKit 2 app set up by an earlier release. @zitadel/sdk-sveltekit now peers on @sveltejs/kit 3 only. Upgrade to SvelteKit 3 first.

Patch changes

  • The AGENTS.md section zitadel setup writes now says the generated pages and request boundary are the app's to change, as zitadel doctor treats them, and points at the documented --zl-* tokens and ::part() names for theming; the CLI's agent skill says to capture output outside the app directory. (#2289)

  • Every question the CLI asks now reads as a plain question, such as "What is the client ID of your Google OAuth app?" instead of "Client ID", and the client secret question says which project variable will hold it. (#2366)

  • zitadel doctor no longer checks a local Zitadel runtime for a project set up against a hosted server, so another app on port 8080 no longer fails it with E_PORT_IN_USE. (#2368)

  • The AGENTS.md section zitadel setup writes now says a page heading shown in place of the login's own has to follow its zitadel-flow-step event, since the login moves between sign-in and sign-up by itself, instead of suggesting a fixed heading that read "Sign in" during a sign-up. (#2368)

  • zitadel setup creates React, Vue, Solid, Svelte and Qwik apps again: it pins create-vite 9.3.1, since 9.3.2 was published without its templates and setup then failed with "Could not detect a supported app framework". (#2386)

  • When the server has no endpoint for a command, the CLI now says that the server and the CLI are different versions instead of reporting a missing resource, and still tells apart a --server that is not a Zitadel API. (#2299)

  • zitadel setup asks fewer, clearer questions: your app's port only when that port is taken, and the server with the local one listed even when stopped. An empty client ID skips that sign-in provider and prints the zitadel auth-method sso enable command that adds it later, and Ctrl-C at any prompt exits with code 130 and no error. (#2371)

  • Commands the CLI suggests running next now keep the run's --env, --env-file or --server, so following one targets the same project instead of the default one. (#1649)

  • zitadel start now waits until the local server's database answers, not only its process, and zitadel doctor, zitadel status and zitadel setup count a local server as healthy only then, asking its /readyz instead of /healthz. (#2308)

  • zitadel setup --use-case consumer and --use-case business now make a user's given and family name their display name (x-display), so a signed-in session reads "Ada Lovelace" instead of only the email, in getSession() and <zitadel-session>. (#2368)

    Upgrading a project set up earlier: add "x-display": ["givenName", "familyName"] to its user schema and run zitadel deploy; users who sign up after that show their name.

  • zitadel doctor now warns when the editor schemas in .zitadel/meta/ are missing or outdated, which leaves a file's $schema pointing at nothing or at old rules, and doctor --fix writes the current ones; branding eject writes .zitadel/meta/branding.json the same way. (#2326)

  • <zitadel-login> now says why it switched between sign-in and sign-up by itself: an email without an account becomes a sign-up, with a note to fix a typo in the email field, and one with an account a sign-in; an identity provider sign-in without an account gets the same note. Flow API steps carry purpose and, after a switch, notice. (#2373)

    Upgrading a page or design from an earlier release: a page that shows its own heading in place of the widget's should follow step.purpose from the zitadel-flow-step event, not step names, and a design ejected earlier shows the notice once its template renders messages, as the designs zitadel branding eject copies now do.

  • Adding a route to protectedRoutes alone now requires a sign-in on it: zitadel setup writes a Next.js proxy.ts (middleware.ts on Next.js 15) whose matcher covers every route but Next.js's build files; before, a route missing from the matcher stayed public without a warning. (#2372)

    Upgrading an app set up earlier: it keeps its matcher; replace it with ["/((?!_next/static|_next/image|favicon.ico).*)"], or add each protected route to it.

  • Updated dependencies: @zitadel/server@0.2.0, @zitadel/api@0.2.0, @zitadel/config@0.2.0.

0.1.0

Minor changes

  • Add zitadel api, which calls any operation of the Zitadel API with your project's server and secret, including the ones no command covers yet. Name an operation by its operationId (zitadel api queryUsers) or its path (zitadel api /users/query), set query parameters and body fields with -F name=value, typed by the operation's schema, pass a whole body with --input, follow every page with --paginate, and preview the request as cURL with --dry-run. zitadel api ls lists the operations, and --json returns the response body as data. @zitadel/api exports the operations as data from @zitadel/api/generated/operations: each one's method, path, parameters, authentication and paging. (#2187)

  • Releases now publish to npm's latest tag as 0.x preview versions, starting at 0.1.0, instead of 1.0.0-alpha.N under alpha: run the CLI as npx @zitadel/cli@latest and install the SDKs with @latest. The 1.0.0-alpha.N versions are deprecated on npm. Until 1.0.0, a breaking change ships in a minor version, and its release note marks it Breaking:. zitadel setup pins the @zitadel/* packages it adds, and the commands it suggests, to the CLI's exact version on every release, and zitadel start --runtime docker runs the server image of that version. (#2215)

Patch changes

  • zitadel setup now patches an existing SvelteKit, SolidStart, Qwik City or TanStack Start app instead of failing on it. The four patchers wrote the app shell and landing page (SvelteKit's +layout.svelte and +page.svelte, the other three's routes/index.tsx) on every run, so setup stopped with E_CONFLICT on any app that already had them and --force overwrote the user's own root layout and homepage. Like the Next and Nuxt patchers, they now write those files only when setup created the app skeleton itself and report them as conditionally scaffolded, so a pre-existing app keeps its shell and landing page while still getting the auth boundary and the login, register and profile routes. (#2190)

  • Add @zitadel/sdk-next/components, a "use client" entry that re-exports the React ZitadelLogin, ZitadelLogout and ZitadelSession wrappers, so project and locales reach the element as properties after server-rendered hydration on React 19 and on React 18, where the raw elements lose them or write [object Object] attributes. The Next.js pages zitadel setup scaffolds now render the wrappers. (#2177)

  • Updated dependencies: @zitadel/api@0.1.0, @zitadel/server@0.1.0, @zitadel/config@0.1.0.

1.0.0-alpha.28

Minor changes

  • zitadel setup --dev-port <port> now moves an app that's already set up to another port instead of skipping it. It allows the new origin on the server, then updates the dev script or dev-server config, ZITADEL_ISSUER in .env.local and zitadel.json together, so an app whose port is taken (EADDRINUSE) signs in on the new port after one command. The README and AGENTS.md sections setup writes name this command. (#2157)

  • The dev proxies zitadel setup writes for React, Vue, Angular, Solid, Svelte and Qwik apps now keep the app's session in a cookie named after its project, __nextgen_session_<project id>, as the server SDKs do. On localhost, signing in to such an app no longer signs you out of the Console of zitadel start, and signing in to the Console no longer signs you out of the app. The project comes from ZITADEL_PROJECT_ID in .env.local; without it the proxy passes cookies through as before. (#2071)

    For an app set up with an earlier CLI, run zitadel doctor --fix: it adds this to the dev proxy the CLI wrote, and zitadel doctor warns until then. (Running zitadel setup again leaves an app that is already set up alone.) A dev proxy changed by hand since is left as it is, and zitadel doctor says the hooks must be added by hand. A dev session from before the change is not carried over, so sign in to the app once more.

Patch changes

  • Built-in schemas are now named under https://zitadel.com/schemas/v1, for example https://zitadel.com/schemas/v1/user-schema.json, instead of https://nextgen.com/api/schemas, a domain Zitadel does not own. The base is an identifier, not an address: the server never fetches a URL in either namespace. Existing projects need no change: a built-in schema's id resolves under either base everywhere the API reads one, including GET /schemas/{id}, a flow definition's user_schema, a release's schema pointer, the users list's schema filter, and bootstrap user documents. A server still configured with the previous base accepts the new ids too. Projects created by zitadel setup from now on reference the new URLs, so pair this CLI with a server from this release or later. (#2056)

  • zitadel setup without --server, and the setup wizard's Zitadel Cloud choice, now target the hosted preview at https://preview.zitadel.com instead of https://api.zitadel.cloud. A project whose zitadel.json names a server keeps using that server. (#2056)

  • A server zitadel start runs can now sign users in through an identity provider on this machine, such as a test provider on http://localhost:9400. start passes NEXTGEN_HTTPCLIENT_ALLOW_LIST=localhost,127.0.0.0/8,::1/128 to the server unless .env.local, .env or the shell sets it; set it empty to turn it off. auth-method sso enable no longer warns about a localhost issuer on such a server, and on a --runtime docker server it warns that the container cannot reach this machine's localhost. (#2144)

  • zitadel plan shows an edit inside a list as the lines that changed. A flow's steps and a step's actions are matched by name, so adding a field to one step prints that one line under the step, with the untouched steps summarized as hidden, instead of the whole steps list removed and added again. Lists of values such as fields mark the items added or removed. (#2153)

    zitadel apply and zitadel setup no longer rewrite your .zitadel/ files with what the server stored: after applying a connection, .zitadel/idps/*.json no longer gains pkce_enabled, token_endpoint_auth_method and id_token_mapping. A field you leave out reads as the server's default, so plan reports no change after an apply. The one file apply still rewrites, and lists in files_updated, is a flow whose user_schema pins a schema revision id the apply superseded.

  • A Next.js app created by zitadel setup --framework next no longer logs Could not validate `instant` errors in next dev on every request to /. The home page setup writes still redirects to /login, and now opts out of Next.js's instant-navigation check, which Cache Components (on by default in new Next.js apps) runs on every page. The @zitadel/sdk-next README's auth() examples now render the component that calls auth() inside <Suspense> and import it from @zitadel/sdk-next/server, so they build with Cache Components on. (#2145)

  • The /login, /register and /profile pages that zitadel setup writes for a new Next.js app paint the sign-in page's dark background on the first paint, so they no longer show a white page before the sign-in form loads. (#2148)

  • Editors no longer flag zitadel.json for an unreachable schema. zitadel setup now writes the schema of zitadel.json to .zitadel/meta/project.json, next to the flow and user-schema dialects, and points the file's $schema at it, so editors validate the project configuration offline instead of trying to load https://schemas.zitadel.com/v2/project.schema.json, which never existed. In a project set up earlier, delete the $schema line from zitadel.json to silence the warning; only editors read it. (#2151)

  • The Qwik meta-framework SDK is @zitadel/sdk-qwik-router, named after Qwik Router, which Qwik 2 renamed from Qwik City; it was never published as @zitadel/sdk-qwik-city. The CLI calls the framework qwik-router too (zitadel setup --framework qwik-router), and its docs page moved to /docs/sdks/qwik-router. (#2077)

  • The server SDKs now count a session as signed in only when it belongs to the app's own project. A session of another project on the same server, such as a second app on the same host or the console of a local zitadel start, reads as signed out in protectedRoutes, auth() and getAuth(), and its cookie is left in place for the app it belongs to. (#2076)

    The project comes from the new projectId middleware option, else ZITADEL_PROJECT_ID, else the framework's public project id variable (NEXT_PUBLIC_, VITE_ or PUBLIC_ZITADEL_PROJECT_ID). zitadel setup already writes these to .env.local. A deployment that sets none of them must set ZITADEL_PROJECT_ID, or nobody is signed in. With @zitadel/sdk-nuxt, the public project id (NUXT_PUBLIC_ZITADEL_PROJECT_ID) decides unless nextgen.projectId names one, so a built server follows it at runtime. A JWT session token must name the project in its aud claim. auth() in @zitadel/sdk-next also takes the project of configureZitadel(); with createProxy(config), pass the same project to auth() when it lives only in that handle.

    GET /sessions/me takes an optional project_id and answers a session of another project with 403 sess.project_mismatch. The SDKs send it, and so do getSession() in @zitadel/sdk-next, <zitadel-session>, <zitadel-logout> and the console. getSession() takes the project from its projectId option, configureZitadel() or NEXT_PUBLIC_ZITADEL_PROJECT_ID, and without one reads as signed out. Signing out with <zitadel-logout> or <zitadel-session> no longer ends another project's session. The AGENTS.md guidance that zitadel setup writes now tells agents to read the session this way.

    A Bearer token that is not this app's no longer decides what happens to the session cookie that came with it: the cookie is judged, and cleared only when the backend refuses it, on its own.

    In @zitadel/api, getMySession() now takes its query parameters first: call getMySession(undefined, init) where you called getMySession(init). In @zitadel/sdk-core, checkOpaqueSessionToken() and validateOpaqueSessionToken() take the project id as a fourth argument, and resolveSession() is the one implementation of these rules behind every server SDK's middleware.

    The @zitadel/sdk-next middleware also no longer lets a session that has not finished signing in through protectedRoutes.

  • Updated dependencies: @zitadel/server@1.0.0-alpha.28, @zitadel/api@1.0.0-alpha.28, @zitadel/config@1.0.0-alpha.28.

1.0.0-alpha.27

Patch changes

  • zitadel setup now creates new Next.js apps on Next.js 16.4.2 instead of 16.2.4. Next.js 16.2.4 has known advisories, among them a critical remote code execution and several ways around Middleware / Proxy, the layer nextgenMiddleware protects routes in. An app created by an earlier zitadel setup keeps the version it was created with: upgrade it with npm install next@^16.4.2. (#2054)

  • zitadel setup no longer leaves a half-set-up app behind in a directory it creates a new app in. It creates the app only once it has every answer, so cancelling a question leaves the directory as it was. When a later step fails, such as an unreachable server, a failed upload of the default schema, or Ctrl-C, setup removes the app again and puts back what the directory held. The next run then sets up a new app, with the / → /login redirect, instead of treating the leftover skeleton as an app that existed before. In an empty directory, --dry-run no longer runs the framework's generator (such as create-next-app): it previews the project and writes nothing. (#2053)

  • zitadel stop and zitadel reset no longer fail with spawn docker ENOENT on a machine without Docker. The default runtime does not need Docker, and where Docker is not installed there is no container to remove, so both now stop or reset the directory's local server as usual. zitadel start likewise no longer fails when it moves a directory that used --runtime docker back to the default runtime after Docker was uninstalled. (#2058)

  • Updated dependencies: @zitadel/server@1.0.0-alpha.27, @zitadel/api@1.0.0-alpha.27, @zitadel/config@1.0.0-alpha.27.

1.0.0-alpha.26

Minor changes

  • Add the zitadel auth-method commands, one per sign-in method: auth-method password enable|disable, auth-method passkey enable|disable, and auth-method sso enable|disable --provider <name>. auth-method sso enable takes --provider google, or --provider custom with --slug, --display-name, --issuer and optional --scopes for any other OpenID Connect provider. zitadel auth-method sso disable is new: it removes a provider from the schema and the login flows, and keeps its connection and credentials; a custom provider is named by its slug, as in --provider acme-sso. zitadel sso enable is removed; run zitadel auth-method sso enable instead, with the same flags. The commands change only local files, apart from auth-method sso enable, which also stores the provider's credentials on the project; plan and apply publish the schema and flow changes. Before writing or publishing anything, they refuse: (#1492)

    • disabling a method that a login flow still asks for;
    • changing a method while a flow has errors that prevent checking it;
    • disabling the schema's last way to sign in, unless you confirm in a terminal or pass --force (meant for a schema whose users are only managed through the API);
    • enabling password on a schema without x-identifier;
    • editing a value that is not the shape these commands edit, or a schema that points at an external URL.

    The console's empty identity-provider list now tells you to run zitadel auth-method sso enable.

  • Add SvelteKit, TanStack Start, SolidStart and Qwik City meta-framework SDKs, mirroring sdk-next and sdk-nuxt. Each ships a server integration that proxies /__nextgen/* to the auth backend (attaching the project secret to the POST /sessions/exchange handoff only, and forwarding the identity-provider callback's redirect onto the app), verifies the session JWT via JWKS with the Web Crypto API, and redirects unauthenticated requests on protected routes, plus a getAuth helper. The SvelteKit, SolidStart and Qwik City SDKs are server-only; their pages render the login, logout and session widgets with the matching framework component SDK (@zitadel/sdk-svelte, @zitadel/sdk-solid, @zitadel/sdk-qwik). The server glue uses each framework's native primitive: a SvelteKit handle hook (createNextgenHandle), a SolidStart onRequest middleware (createNextgenMiddleware), a Qwik City RequestHandler (createNextgenOnRequest), and a TanStack Start request middleware (createNextgenRequestMiddleware, with the framework-agnostic handleNextgenRequest core exported too). All JWT verification and proxy logic is shared from @zitadel/sdk-core. (#373)

    Like the Nuxt SDK, these SDKs sign in only a session that names a user. The backend sets the session cookie before a sign-in verifies any factor, so an anonymous session reads as signed out, and its cookie is kept so its sign-in can still complete. The signed-in session carries the user's identifier, identifierProperty and display, and a dead session cookie is cleared on every unauthenticated response. protectedRoutes guards full page loads, while the ZitadelSession card covers client-side navigation; each README shows how to guard a page on the server before it renders: a +page.server.ts load in SvelteKit, a preloaded server query in SolidStart, a routeLoader$ in Qwik City and a beforeLoad server function in TanStack Start.

    @zitadel/sdk-core/middleware exports validateOpaqueSessionToken, the backend session check these SDKs share, and requiresProjectSecret, the rule that only the POST /sessions/exchange handoff carries the project secret.

    @zitadel/sdk-tanstack-start also ships the widgets, so a TanStack Start app installs no other Zitadel package. Its /react entry exports @zitadel/sdk-react's ZitadelLogin, ZitadelLogout and ZitadelSession components (every event callback, children slots and the signed-in session card), businessLocales, and every type @zitadel/sdk-react exports, such as ZitadelFlowCompleteDetail for typing a callback. Because TanStack Start server-renders every route, the components render nothing on the server and during hydration and mount right after with every prop in place, so a widget never starts a flow with its defaults. The /client entry exports configureZitadel, getApi and getZitadelConfig. The README's src/start.ts example registers createCsrfMiddleware ahead of auth, because adding that file turns off TanStack Start's automatic CSRF protection for server functions.

    sdk-sveltekit peers @sveltejs/kit ^2.0.0 || ^3.0.0 and svelte ^5.0.0, the Svelte range @zitadel/sdk-svelte supports. sdk-solid-start peers solid-js ^1.8.0, the range @zitadel/sdk-solid supports.

    sdk-qwik-city targets Qwik 2: it peers @qwik.dev/core and @qwik.dev/router (^2.0.0-beta.45), like @zitadel/sdk-qwik, whose widgets its pages render. A Qwik 1 City app (@builder.io/qwik-city) must migrate to @qwik.dev/* first. It reads ZITADEL_URL and ZITADEL_PROJECT_SECRET per request from Qwik City's ev.env, which carries .env.local, falling back to process.env.

    The CLI now detects, scaffolds, and integrates all four meta-frameworks end to end. New detectors recognise SvelteKit (@sveltejs/kit), SolidStart (@solidjs/start), Qwik City on Qwik 2 (@qwik.dev/router) and TanStack Start (@tanstack/react-start) and run before their base-SPA detectors so a meta-framework app is no longer mistaken for a bare SPA (the React detector now also excludes TanStack Start). Each gains a scaffolder and a patcher that wires the framework-native server entrypoint (src/hooks.server.ts; src/middleware.ts registered via the solidStart() plugin in vite.config.ts; src/routes/plugin@nextgen.ts; and a src/start.ts request middleware respectively), the landing/login/register/profile routes rendering the widgets from the framework component SDK (from @zitadel/sdk-tanstack-start/react for TanStack Start), the public project-id env var, and the matching @zitadel/sdk-* dependency. SvelteKit pages read the project id from VITE_ZITADEL_PROJECT_ID, which setup seeds in .env.local. The generated /profile page renders the "signed in as" session card with sign-out, and setup --use-case business wires the businessLocales work-email copy into the login and register pages. Setup pins the app's dev script to the port it registers as the project's origin, with --strictPort, and refuses a SolidStart 1 app up front with the reason. doctor treats the auth entrypoint, and SolidStart's vite.config middleware registration, as managed infrastructure and fails when either is missing.

  • Add a custom OpenID Connect provider to the CLI. zitadel sso enable --provider custom takes a connection name (--slug), a sign-in button name (--display-name), an --issuer and optional --scopes (openid required, defaulting to openid profile email), and writes .zitadel/idps/<slug>.json, enables it on the user schema and adds it to every login flow, as it does for Google. The connection is identified by its slug rather than its issuer, so a Project can hold several connections to one issuer; a name already used for a connection to a different issuer is refused. zitadel setup offers the same as "another OpenID Connect provider", with --sso custom and --sso-slug, --sso-display-name, --sso-issuer and --sso-scopes for scripted runs. The connection maps the standard OpenID Connect claims the schema defines and trusts email only when the provider reports email_verified; other mappings are made by editing the file. Adding a second provider after apply no longer reports the conflict and registration steps as hand-edited because the platform filled in their empty gates and sso_providers. @zitadel/config/idp no longer exports ISSUER_REQUIREMENT or isSupportedIssuer; use parseIssuer, which returns the issuer or why it is refused. idpProvider and IDP_PROVIDERS now cover only the catalog's own providers, IdpProvider.template is string | undefined because a custom connection names no template, and selectionSlug is gone in favour of providerOf(selection).slug. (#1698)

  • The resource commands now take every field of a body as a flag, nested ones included: a nested field's flag is its path joined with -. zitadel idps create takes --slug, --display-name, --oidc-issuer, --oidc-client-id, --oidc-client-secret and --oidc-scopes instead of only a JSON document. zitadel grants create names its principal with --user-identifier or --team-name, and zitadel projects update sets the password hashing method with --password-hash-algorithm and --password-hash-params-*. A boolean takes --no-<flag> for false, and a list is a repeated flag. --set <path>=<value> (or <path>:=<json>) sets any field by its dot path, and = reads the value as the type the field has, so --set oidc.pkce_enabled=false is a boolean. zitadel idps create --file and --data take the connection document itself, the same shape as .zitadel/idps/<slug>.json; a body still wrapped in idp is accepted too. (#1865)

    • A field flag over --file or --data now changes only the key it names: --attributes givenName=Ada on top of a file no longer drops the file's other attributes.
    • A ${{ NAME }} variable reference is no longer refused as a credential, so zitadel idps create --data works. Booleans, enum values, declared numbers and endpoint URLs such as token_endpoint are no longer refused or redacted either. Free-form text in a field named like a credential is still refused, now also from the field flags and --set; write references in single quotes so the shell leaves ${{ alone.
    • --dry-run shows those values instead of «redacted», so the body it prints can be saved and sent later with --file.
    • zitadel resources --json lists every nested field with its dot path, flag, kind and whether it is required.
  • setup now pins every framework's Zitadel SDK at the CLI's own version through one rule, as it already did for Next.js, instead of the floating alpha tag. The app stays on the SDK that matches the files the CLI wrote, and doctor warns when the CLI and the pinned SDK drift apart. (#2009)

  • zitadel plan and zitadel apply now check the SSO configuration across files before anything is sent, and stop with one error that names the file, the steps and the name at fault, and what to change. It reports what the plan touches and the files those reference, so an older mistake in an unrelated file does not stop an edit. It catches a step that offers a provider its user schema does not list, or when SSO is off; a schema that lists a provider with no connection file; a step that offers providers but does not route every outcome (a user_already_exists route that carries an action does not count); a claim mapping that fills nothing the schema defines; a mapping or verified_claims key that names a property no schema has (a top-level name, not a dotted path); and a verified_claims key with no claim_mapping row. Two connection files with the same slug or the same project variable stop plan and apply too, as part of that check. A connection that cannot create users needs only sso_authenticated routed and no claim mapping. @zitadel/config/validate is now one validation library: validateFlowDefinition for a single flow and its schema, and the new validateProject for the whole working tree, with every rule named once in a typed registry. FlowValidationRuleId is now FlowRuleId; the old name stays as a deprecated alias. (#1703)

    Connection files are held to the platform too, and these checks are not turned off by ZITADEL_SKIP_FLOW_VALIDATION, which switches off flow validation and the cross-file check above. A client_id or client_secret that names a project variable must find it: a missing one fails with E_CREDENTIAL_MISSING, a client secret that is not a secret variable, a client id that is a secret one, or one variable for both is refused. Editing the slug, protocol, oidc.issuer, the OAuth2 endpoints or subject_claim of a connection that was already applied (an absent OIDC subject_claim reads as sub) is refused with the field and file named. plan stops when the project's variables or an edited connection's stored copy cannot be read, instead of skipping the checks.

    A removed connection file is reported apart from the changes, because the platform cannot delete it yet: plan --json and apply --json carry unsupported_deletions (a count, with the files in unsupported_deletion_files on apply), the headline no longer says "no changes", and apply no longer says everything is in sync. A renamed connection file is planned and reported as a move (move in changes, with moved_from), shown as a diff against the stored connection.

  • Project create/get/query now return typed origins (pattern + kind) instead of preview_origins. Nested POST /projects/{id}/origins and .../origins/remove are available (server returns 501 until the origins service ships). (#1677)

  • Environments are gone. GET /environments, GET /environments/{name} and the environment.created event are removed, the CLI drops zitadel environments list and zitadel environments get, and projects are created without the default dev, staging and prod environments. Variables belong to the project alone: environment_name on the variable operations answers env.not_found. The deployment operations answer 501 until deployments target origins. (#1643)

  • Show a one-time banner, at the top of zitadel and zitadel setup, when AI coding agents (Claude Code, Cursor, Gemini CLI, and others) are detected, telling the developer to run npx skills add zitadel/nextgen --full-depth to give them the Zitadel skills. The CLI installs nothing itself. The banner is skipped in CI, with --json or --non-interactive, and when NO_SKILLS_PROMPT is set. (#1687)

Patch changes

  • The zitadel-cli agent skill now points agents at the sign-out button that apps generated by zitadel setup actually render, zitadel-session-logout inside <zitadel-session>, instead of <zitadel-logout>'s .signout-btn, which those apps don't render, and describes typography.font_url, which it said could not be set yet. It also says where next_commands sits in the JSON envelope, and tells agents to always pass @alpha, because bare npx @zitadel/cli runs an early 0.0.0 build. The @zitadel/components README now lists the sign-out hooks of <zitadel-session> and says how to reach the sign-out button of <zitadel-logout>, which has none. (#1730)

  • setup now checks the running Node against the engines.node of the framework CLI before it creates a new app, for every framework, with one shared check that each scaffolder configures. The range is asked of npm for the exact CLI that setup runs, gives up after 5 seconds and then uses a built-in range, so an unreachable registry no longer holds setup up. For Angular it refuses a Node the Angular CLI would refuse, with a message that names the Node versions Angular needs (for Angular 22, ^22.22.3 || ^24.15.0 || >=26.0.0, which the Angular CLI applies one major at a time, so only 22.x, 24.x and 26.x at or above those versions are accepted and Node 27 is refused), and only warns on Node 23 and 25, which the Angular CLI runs on with a warning; previously the refusal surfaced only as a generic "exited with status 3" error. Next.js, Nuxt, React, Vue, Solid, Svelte and Qwik never refuse, because their CLIs carry on or only warn on an unsupported Node, but setup now adds a warning to the envelope's warnings when the Node is outside the range. (#1927)

  • setup moves a dependency from devDependencies into dependencies instead of listing it in both. A package the app already had in devDependencies was added a second time, and some toolchains refuse a package listed twice. A runtime dependency is never demoted: when the app already lists a package in dependencies and a dev add arrives, it stays where it is, and peerDependencies and optionalDependencies entries are left alone. (#1917)

  • Every framework detector now declares its minimum version and one shared check enforces it, so setup and doctor refuse Nuxt below 4, Vue below 3, Angular below 17, Solid below 1.6 and Qwik below 2.0.0-beta.45 (the first release the SDK accepts) up front, as they already did for Next.js, React and Svelte. The SDK peer ranges already refused these at install time; the CLI now says so at setup with an upgrade hint. Angular below 17 now fails with E_UNSUPPORTED_PROJECT_SHAPE instead of E_VALIDATION, and every framework is also judged by its installed version, not only Next.js. (#1954)

    • A Svelte app on Svelte 4 is refused up front with the reason, instead of failing later on an uninstallable Svelte 5 component library. (#373)
    • The setup summary names every framework with its version ("Angular 20.1.0 · TypeScript · npm") instead of the raw id; Angular, Solid and Qwik apps showed no version at all.
  • zitadel setup no longer writes the server URL and the project id into a Nuxt app's nuxt.config.ts as fixed fallbacks, so the app follows ZITADEL_URL and NUXT_PUBLIC_ZITADEL_PROJECT_ID in .env.local, as Next.js and Vite apps do. It also stops writing the unused runtimeConfig.zitadelUrl and runtimeConfig.public.nextgenProxyPath. An app set up earlier keeps its config: remove nextgen.url and runtimeConfig.public.zitadelProjectId from its nuxt.config.ts to follow .env.local too. (#1759)

  • The Nuxt scaffold no longer adds @zitadel/api, @zitadel/components and @zitadel/design-tokens to build.transpile in nuxt.config.ts. The app depends only on @zitadel/sdk-nuxt, which owns those packages, and builds and renders the login page without the entries. Apps scaffolded earlier keep their entries, which are harmless, and doctor and eject still recognise their config as wired. (#2020)

  • The Nuxt scaffold now depends only on @zitadel/sdk-nuxt, never on @zitadel/components. Its pages import nothing from @zitadel/*, and the business copy overlay comes from useNuxtApp().$zitadelBusinessLocales, which the SDK module provides. Setup no longer writes plugins/zitadel-components.client.ts or plugins/auth.server.ts, because the module loads the widgets and seeds the auth state itself. eject still removes both from apps an older setup wrote, and doctor --fix drops their records from the scaffold manifest once they are deleted. This needs the SDK release that makes the module load the elements. (#2015)

  • zitadel setup and zitadel sso enable show each connection's own redirect URI, {issuer}/__nextgen/idp/{slug}/callback, for example http://localhost:3000/__nextgen/idp/google/callback. The shared /__nextgen/idp/callback the previous release printed no longer works: rerun zitadel sso enable --provider google to see the new URI, which it now prints for an existing connection too, and register it with the provider. setup --json reports it as callback_uri on each sso entry, as sso enable --json already did. (#1633)

  • zitadel setup and zitadel doctor now require Next.js 15.2.3 or later. Next.js 15.0.0 to 15.2.2 skip middleware for a request that sends an x-middleware-subrequest header (CVE-2025-29927), so on those versions anyone could reach a page that protectedRoutes guards. A version that can only resolve below 15.2.3, such as 15.2.2 or ~15.1.0, is refused with E_UNSUPPORTED_PROJECT_SHAPE, and so is an app whose installed Next.js is older than 15.2.3 even when its range, such as ^15.0.0, allows a fixed release. Upgrade Next.js and reinstall, then rerun the command. (#1954)

  • setup and doctor refuse a Svelte 4 app up front, saying it needs Svelte 5 or newer, instead of failing later on a Svelte 5 component library that cannot be installed. (#1913)

  • zitadel idps create --help now suggests --file ./identity-provider-connection.json, an example you can run as written: the file name it showed before had spaces in it, so a shell split it into three arguments. The help for zitadel users create, users update and users delete now says "a user" instead of "an user". (#1866)

  • The setup summary names every framework with its version, such as "Angular 20.1.0 · TypeScript · npm". Angular, Solid and Qwik apps showed the raw framework id and no version. (#1910)

  • Every question the CLI asks now goes through one input function, so a missing value, a missing --force and a cancelled prompt read the same in every command. A command that cannot ask now says "No <value> supplied" with a hint naming the flag to pass or the stdin to pipe, a destructive command without --force says it cannot proceed without it, and Ctrl-C at any prompt reports "Cancelled by user" instead of naming the command. Cancelling the confirmation of variables delete or a resource delete now stops with that error instead of reporting a skipped delete, and eject now asks for confirmation in a terminal when --force is not given. (#1766)

  • Offer the sign-in provider on the password step. Someone who signed up with a provider has no password, and typing their email instead of clicking the button sent them to a password step with only a password field and no way out. zitadel sso enable and zitadel setup now add the enabled providers to that step too, and rerunning sso enable adds them to an existing project's unedited password step. A password step edited by hand is left alone and reported, like the other generated steps. (#1701)

  • setup and doctor now refuse a Vue app below 3.2, matching the Vue SDK's peer range, so a project on Vue 3.0 or 3.1 gets the upgrade hint instead of an SDK that fails at runtime. (#1948)

  • The zitadel plan warning about a flow definition becoming the project default for <zitadel-login> now gives advice that keeps the current default: give a new flow definition an audience before its first apply and select it by flowName, or pin flow-name on the pages that should keep the current project default. It used to suggest setting flow-name on the pages that should use the new one, which leaves it the project default on every other page. (#1756)

  • doctor run in an app inside the directory zitadel start ran in now checks that local server — the one setup attached the app to — instead of the default port 8080. It read local runtime state from the current directory only, so it reported "No existing runtime metadata", counted the app's own server as another runtime, and failed with E_PORT_IN_USE whenever anything else held port 8080. It now finds the nearest started server on the way up, like setup and console, and --port defaults to that server's port. When that server is unhealthy, or its record is malformed, the suggested start and reset --force now carry --cwd for the directory that holds it, instead of acting on the app directory. When that directory needs shell quoting, as a Windows path with a space does, the commands are left out and the exact arguments are in details.suggested_args. (#1914)

  • doctor now checks that package.json declares the SDK packages the app actually imports, such as @zitadel/sdk-next, or @zitadel/sdk-sveltekit plus @zitadel/sdk-svelte. Before, any @zitadel/* package passed, so an app that kept only the CLI as a dev dependency was reported healthy. The check names the missing package and doctor --fix re-adds it. The setup docs and the CLI reference now list all twelve --framework ids and the framework versions setup supports. (#2010)

  • A flow definition names its user schema by objectType and runs against the project's newest revision of it. POST /flow_definitions still accepts a schema id and stores the objectType it names, so reading a definition back returns the objectType. (#1683)

    The default login flows name human-user, and getDefaultLoginFlow no longer takes userSchemaUrl. zitadel sso enable finds a flow that names its schema by objectType, and the console links a flow to the revision it runs against.

  • Give each directory its own local server and port, for the binary and the Docker runtime alike. zitadel start now keeps the port a directory already had, and otherwise takes the first free port from 8080, so two projects no longer compete for 8080. It checks that a port is free by trying to use it, not by parsing lsof, and it refuses an explicit --port that is taken instead of reporting another folder's server as its own. zitadel console, zitadel setup and the other commands only use the server named in the directory's own .zitadel/local/runtime.json, and no longer fall back to whatever answers on port 8080; when a directory has none, the error says to run zitadel start or to name a server with --server. zitadel start runs the server binary directly instead of through a wrapper, and zitadel stop now also stops a server whose recorded process has already died, and says plainly when a directory has no server to stop. @zitadel/server now exports @zitadel/server/binary, which finds the server binary for a platform, so the CLI and the wrapper share one list of platforms. (#1864)

  • zitadel grants create --json for a user named by user.identifier now includes data, an empty object, in its JSON envelope. The server accepts that request with 202 Accepted and no body, and the envelope used to leave data out. Run zitadel grants list to see whether a grant was written. (#1756)

  • Identity provider connections are OIDC only. The oauth2 protocol is removed from the connection schema and the API, together with the supplementary_fetch strategy and the "$supplementary_fetch" value in verified_claims. The engine never served either, so such a connection showed a sign-in button that could not work. (#1809)

    Breaking for stored data: the server's next start deletes every oauth2 connection stored by an earlier release, with its revisions and identity links.

    POST /idps now refuses, at create and at revise, an OIDC connection the engine could not start a sign-in with: an oidc block that sets an endpoint without the others fails with 400 idp.endpoints_partial, and the error details list the missing endpoints. Set no endpoint and let discovery fill them, or set authorization_endpoint, token_endpoint, jwks_uri, and userinfo_endpoint (userinfo_endpoint may be left out when id_token_mapping is on). Nothing is stored, and a revise leaves the current revision unchanged. zitadel plan reports the same problem before anything is applied, and zitadel apply names the missing endpoints when the server refuses one.

  • A Nuxt app set up by the CLI no longer logs two warnings in dev. The SDK plugin stops reading its private runtime config in the browser, which Nuxt reported as NUXT_E1003, and the scaffolded home page now has a template, so Vue stops warning that it has nothing to render. (#2029)

  • Update dependencies that have published security advisories. The server picks up the HTTP/2 and gRPC denial-of-service fixes, the OpenTelemetry exporter fixes (the log exporter's TLS settings, endpoint URLs in logs) and the golang.org/x/crypto SSH fixes. @zitadel/cli now requires undici 7.29.1 or later and @zitadel/components DOMPurify 3.4.16 or later, and the optional @faker-js/faker peer of @zitadel/api moves to ^10.5.0. (#1845)

  • zitadel setup now scaffolds a new app into the directory zitadel start just ran in. The local runtime and the .gitignore line that start writes no longer make the directory count as non-empty, so start and then setup --server local work in a new directory without --force. (#1741)

  • zitadel setup now checks whether patching your app would overwrite one of your files, such as an existing app's src/App.tsx, before it creates a project. It stops with E_CONFLICT without creating a project or writing zitadel.json, so you can move the file aside and rerun, or rerun with --force to let setup replace it. Before, setup created the project and wrote zitadel.json first, and every rerun then reported the directory as already set up. (#1730)

  • zitadel setup now stops with E_CONFLICT, before it creates a project, in existing apps it used to leave broken while reporting success: an app that mounts its root component from a file other than the one setup writes, such as src/App.jsx or Angular 17–19's src/app/app.component.ts; a Next.js app whose own middleware.ts would sit beside the proxy.ts setup writes for Next 16, which Next refuses to run; and a --framework other than the app's, where --force used to scaffold a new app over it. The error's hint says what to change, since --force can't fix these. Setup also checks the schema, flow and connection files it writes to .zitadel/ before it creates the project, so a conflict there no longer leaves a project behind whose id and secret a rerun kept in .env.local. (#1757)

  • zitadel setup now puts .env.local back the way it was before the run when it fails after creating the project, for example because the default user schema or login flow did not upload. Before, the file kept the failed run's project id and secret, and since setup only adds the keys .env.local is missing, even with --force, the app set up by a rerun still talked to that abandoned project, which has no user schema or login flow. (#1761)

  • The sign-in picker in zitadel setup no longer says the password-first preset offers a passkey during registration. Its login flow has no passkey step: users sign up and sign in with email and password. (#1730)

  • React, Vue, Solid, Svelte, Qwik and Angular apps set up with zitadel setup --server https://… can now sign in through their dev proxy. The generated /__nextgen proxy sent the app's own localhost host name on to the server, and a remote https server refuses that connection; the proxy now addresses the server by its own name and passes the app's origin on in X-Forwarded-Host and X-Forwarded-Proto. In an app set up with an earlier version, zitadel doctor flags the dev proxy and zitadel doctor --fix updates it in place. (#1721)

  • zitadel auth-method sso enable no longer reports the sso-conflict step as edited by hand when it is what an earlier run wrote for different sign-in methods. Enabling a method such as passkey after the step was first generated now brings the step up to date with the methods enabled today; a step that was genuinely edited is still left alone and reported. (#1843)

  • zitadel start now restarts a local server that an earlier CLI release left running, instead of reusing it, and keeps its data. `zitadel setup --server (#1763)

  • Variables take a kind query parameter, primary or preview, in place of environment_name, and reads return each name's value per kind. A write without kind stores a plain value for both kinds, and a secret for primary only, removing the name's preview value. GET /deployments/{deployment_id}/variables returns the values a deployment froze. Until the server stores kinds, kind=preview and the deployment read answer 501. (#1739)

  • zitadel variables set --help no longer says that a reference to a whole field keeps a number variable a number. The server resolves ${{ NAME }} references only in an OIDC identity provider connection's client_id and client_secret today, and both take a string. (#1756)

  • Updated dependencies: @zitadel/server@1.0.0-alpha.26, @zitadel/api@1.0.0-alpha.26, @zitadel/config@1.0.0-alpha.26.

1.0.0-alpha.25

Minor changes

  • Distribute the CLI's agent guidance as an installable Agent Skill. Any skills-compatible coding agent — Claude Code, Cursor, Copilot, Codex, and more — can now add it with npx skills add zitadel/nextgen --full-depth, giving the agent guided, always-current instructions for scaffolding and managing Zitadel auth with the zitadel CLI. The skill also ships inside the @zitadel/cli package. (#1406)

  • zitadel setup no longer limits onboarding to one social provider. The onboarding question is a multi-select rather than one choice, and declining is an empty selection rather than a "not now" option beside the providers. Each chosen provider is asked for its own client id and secret, gets its own connection file, and has its slug added to the schema and the login flow — which already carried sso_providers as a list. The summary and the --json payload report one entry per provider, so a publish that did not land names the provider it belongs to. --sso still takes a single provider: a scripted run pipes one secret on stdin, and sso enable adds the rest. The catalog ships one provider today, so this changes what the CLI supports rather than what a developer can pick right now — a second catalog entry is what makes the multi-select visible. (#1366)

  • Add social sign-in to the CLI. zitadel sso enable --provider google configures an identity provider on an existing Project: it says what to register with the vendor and at which redirect URI, takes the client id and secret, writes .zitadel/idps/<slug>.json, enables sso on the user schema, and adds the provider button plus the steps and routes a provider round trip needs to every login flow that runs against that schema. The client secret is never a flag — it is prompted for, or piped in on stdin for a scripted run — and never written to disk: only its ${{ NAME }} reference reaches the connection file, and the value is published to the Project as an encrypted variable, which is where the engine resolves it from. zitadel setup asks the same question during onboarding, so a Project can start with Google rather than adding it afterwards, with --sso and --sso-client-id for scripted runs. Everything so far is local: publishing the connection needs identity provider support on the server, which is not in this release, so apply cannot upload a connection file yet and deleting one is not supported either. Enable the provider once that support ships. (#1281)

  • @zitadel/sdk-qwik now targets Qwik 2: it declares a peer dependency on @qwik.dev/core (^2.0.0-beta.45) in place of @builder.io/qwik, so your app must be on Qwik 2 to use it. Qwik 2 runs on Vite 8. The widgets stay reactive to their props, mirroring the other framework SDKs. (#1358)

    The CLI's Qwik support follows suit. zitadel setup now scaffolds and detects Qwik 2 apps: a new app is created on @qwik.dev/core and Vite 8 (no Vite 7 pin), and a Qwik 1 project is no longer auto-detected, since it cannot use the Qwik 2 SDK.

  • setup now decides where to scaffold with a plain rule instead of a maintained allowlist. A directory with a detected framework is integrated in place; an empty directory gets a fresh app; a non-empty directory with no framework stops and asks for --force. setup --force scaffolds a fresh app into a non-empty directory, moving the existing files aside and restoring everything the scaffold does not create. This removes the treadmill of allowlisting each tool's metadata (.gitignore, .zitadel, .claude, …) and lets you install the agent skill into a project directory and then scaffold there with an explicit --force. (#1406)

  • zitadel setup and zitadel sso enable write the renamed SSO outcomes into the login flow: sso_authenticated and sso_user_not_found. user_already_exists keeps its name and is shared with the typed collision. On a flow an earlier version wrote, zitadel sso enable renames callback and identity_unknown to the new keys, so enabling another provider keeps the flow valid. If one of those old keys declares purpose or action, which the new keys refuse, the command stops with a validation error naming the step and the key, so you can remove them first. If a step the command edits already has an action named like an outcome it writes there (sso_authenticated, sso_user_not_found or user_already_exists), whether or not the flow still uses the old keys, the command stops with a validation error naming the step and the action, so you can rename that action first. (#1371)

Patch changes

  • Human output can now be turned colourless with --no-color, and the NO_COLOR and FORCE_COLOR environment variables are honoured. This covers both the step logging and the highlighted commands, paths and ids, so piped and CI runs read cleanly; --color forces colour on. (#1383)

  • A platform request that gets no response is now reported reliably and can be cancelled. (#1456)

    • @zitadel/api: the client throws a typed NetworkError (reason unreachable or timeout) when a request gets no response, alongside ApiError for a failing status. createZitadelClient takes an optional signal and timeoutMs; an aborted request rejects with the signal's reason.
    • @zitadel/cli: a refused, unresolvable or silent server reports E_NETWORK (exit 4) on every command, the same as a server answering 5xx; before, a failure worded differently by the fetch runtime fell through to E_VALIDATION (exit 3). Each request has a 30-second deadline, and Ctrl-C while one is waiting cancels it with the new E_CANCELLED (exit 130) instead of leaving the command hung behind a spinner.
  • Every product command now accepts -v as the short form of --verbose, matching the convention agents already expect from curl, ssh, and wget. The long --verbose is unchanged, and the root --version keeps no short form, so nothing collides. (#1383)

  • Show CLI warnings in both terminal and JSON output. --json results from claim, start, sso enable, setup, plan and apply now include warnings that were only printed to the terminal before. (#1495)

  • Make the local journey work when the app lives inside the directory zitadel start ran in, and say how to continue after setup. (#1429)

    zitadel setup and zitadel console now find the local admin in the working directory or the nearest parent that has one. When a local server has no local admin on that path, setup warns that the project is not attached to a team and that the local console will not list it, and says which directory to run from. The closing box from setup says to open a second terminal for further commands, and lists zitadel console once the project is owned by the local admin. The scaffolded README says the same about the second terminal.

    In the console, the project's Admins section tells an owner that they have admin access as a member of the project's owning team, and an empty list reads "No additional admins have been added."

  • State-changing requests authenticated by the Console session cookie are now protected against cross-site request forgery. A cross-site browser request is refused with 403 auth.csrf_invalid, and every such write except sign-out and the POST …/query reads must also send the session's token (from the new GET /sessions/me/csrf) in the X-Zitadel-CSRF header; the code is listed in each affected operation's default error responses. Requests made with a project secret are unaffected. The generated client adds the header automatically once setApiCsrfToken is set, and only to requests for the origin the token was issued for (the page's own by default). On a refusal it asks setApiCsrfRejectionHandler for a fresh token and retries once with it, so a write survives the session being renewed in another tab; an app returns no token when someone else is signed in now, and the write is then not retried. zitadel setup sends it when it claims a project for the local admin. (#1309)

  • Updated dependencies: @zitadel/api@1.0.0-alpha.25, @zitadel/config@1.0.0-alpha.25, @zitadel/server@1.0.0-alpha.25.

1.0.0-alpha.24

Minor changes

  • Breaking: remove --environment (-e) from plan, apply and the resource commands (users, teams, sessions, events, grants, idps, projects, schemas, environments, releases, flow-definitions, branding). It accepted only development, preview or production — none of them a name the platform uses — and never reached the platform: its one effect was to read a server URL from environments.<name>.server in zitadel.json, a key nothing writes. Passing it now fails as an unknown flag; drop it from scripts. --server, ZITADEL_API_BASE and the top-level server in zitadel.json still choose the server, and environments list / environments get still read the platform's environments. (#1286)

    The CLI therefore has no per-environment flag at all until the platform's environments settle, which is also why the new variables commands address the project level only. Their own --environment / --env never shipped.

  • Setup no longer applies a login template, and the template catalog keeps only widget structure. (#1297)

    • zitadel setup drops the "How should the login look?" question and the --design flag; passing --design now fails with E_VALIDATION and a hint pointing at app-side theming and branding eject. It embeds the maintained login component (a starting page for a new app, a drop-in for an existing one), writes nothing under .zitadel/branding/, and publishes no branding revision. The JSON envelope drops data.design, the summary drops the "Login design" row, and the next actions point at theming the component from your app, with branding eject as the opt-in for owning its template.
    • zitadel branding eject --design now offers centered (the default card) and minimal (the same form without card chrome). split, split-right and hero are removed: they were page layout around the same card, which belongs in your application, and asking for one fails with E_VALIDATION and a hint saying so. BRANDING_DESIGNS in @zitadel/config shrinks accordingly.
    • Revisions already published from split, split-right or hero keep rendering; the login still ships their chrome. The API reference for Branding.layout no longer lists the retired designs.

Patch changes

  • The CLI documentation now explains the local admin admin@zitadel.localhost that zitadel start creates by default, the one-time console sign-in link it prints, the zitadel console command that prints a fresh link, and that on the default local runtime zitadel setup attaches the project to that admin's team, so zitadel claim reports it as already owned. (#1296)

  • Encode path parameters in the generated API client, so any id the API accepts can be fetched. Schema ids are the case that hit today: a schema's id is its $id, usually a URL such as https://nextgen.com/api/schemas/default-human-user.json. Sent raw, the // collapsed on a redirect and the request 404'd, so zitadel schemas get <id> failed for every URL id and the console's user list silently dropped its schema columns. zitadel schemas get also stops guessing what an id looks like: it used to read anything without sch_ or :// as an object type, which sent ids like urn:example:human down the wrong path. It now asks the server, and falls back to the object-type lookup only on a 404. Setup reconciliation and apply/plan schema fetches used to encode the id themselves to work around the same bug; that is gone, so the client encodes exactly once rather than sending %25 for every delimiter. (#1288)

  • Correct the grants command reference. The README still documented --principal-type and --principal-id on grants create, and principal_type / principal_id as filter fields on grants list, which the user and team locators replaced: grants create takes --relation as its only required field flag and names the principal through --data / --file, and grants list filters on user_id and team_id. (#1292)

    grants create --help also no longer suggests grants create --relation viewer. A grant needs exactly one of user or team, which are nested objects no flag can carry, so that run was never a complete body. Any write command whose body needs a nested object or an array now offers only its --data and --file examples.

  • Updated dependencies: @zitadel/server@1.0.0-alpha.24, @zitadel/config@1.0.0-alpha.24, @zitadel/api@1.0.0-alpha.24.

1.0.0-alpha.23

Minor changes

  • Add resource commands to the CLI. users, teams, sessions, events, grants, and projects each gain the operations their API supports — users the full set, teams the same with deactivate in place of delete, sessions list, get and revoke, grants list, get, create and delete, projects list, get and update, and events list and get. idps gains list, get and create, and schemas, environments, releases, flow-definitions and branding gain list and get only: they are authored as files and shipped through a release, so the CLI reads them and never writes them. Every list shares one --filter field=operation:value grammar regardless of whether the endpoint takes a query body or query parameters, with each field declaring the operations it accepts so an unsupported one is refused locally rather than as a server error. Cursor pagination, --fields to choose columns, per-field flags on writes, local schema validation, --dry-run, and a --force guard on destructive verbs are the same everywhere. schemas list and flow-definitions list show the current revision of each rather than the full history, with --filter revisions=all for the history, and schemas get and flow-definitions get accept the object type or flow name as well as a revision id. zitadel resources reports the whole surface in one call. (#1210)

    Breaking: schemas list is now generated from the same registry as every other list. Its --json envelope changes from { object_type, revisions, count } to the standard { items, count, next_page_token }; --object-type becomes an optional --filter object_type=<value> rather than a required flag; and the interactive revision picker is gone, since schemas get <id> reads one revision. A bare schemas list still returns the complete revision history.

  • A branding descriptor now points at its login template with "liquid_template": { "$file": "./login.liquid" } instead of a separate liquid_template_file key. The CLI replaces any $file reference with the file's content before publishing and writes the published value back into the file afterwards. branding eject and setup --design scaffold the new form, and a descriptor that still carries liquid_template_file fails zitadel plan with a hint showing the replacement. (#1224)

  • The project's env files now configure the local server. (#1187)

    zitadel start reads .env.local, then .env, and hands every NEXTGEN_* variable to the server it launches: through the process environment on the binary backend and as bare --env NAME on Docker. No value ever reaches the command line, .zitadel/local/runtime.json, logs, or --json output; only the names are recorded, as env.injected, and zitadel status reports them. The address, data directory and public base the CLI sets itself always win. A running runtime is not updated in place, so after changing a value run zitadel stop and zitadel start.

    setup now writes a comment at the top of the scaffolded .env.example and .env.local saying so.

  • Group the root help by purpose. zitadel, zitadel --help, and zitadel help now list commands under "Project commands", "Local server commands", and "Configuration commands" in journey order, with oclif's own utilities under "Additional commands", followed by flags, examples, and where to learn more, in the layout of gh --help. Nested commands such as schemas list and branding eject are listed inline; the uninstall alias and help are not. Per-command help and --json output are unchanged. (#1249)

  • Identify the CLI in the User-Agent of its HTTP requests — Zitadel API calls, local-server health checks and branding asset probes — so server logs and session records can tell CLI traffic and CLI versions apart. Requests used to carry Node's bare node; they now send zitadel-cli/<version> node/<version> <os>/<release> arch/<arch>, followed by ci/<provider> when running in CI and host/<name> when running under a detected host such as Claude Code, Cursor or VS Code, e.g. zitadel-cli/1.0.0 node/v24.12.0 darwin/24.6.0 arch/arm64 host/claude_code. The ci/ and host/ tokens are left out when telemetry is opted out with --no-telemetry, DO_NOT_TRACK or ZITADEL_TELEMETRY=0. (#1274)

  • Manage per-environment variables and secrets from the CLI with zitadel variables list|get|set|delete. Each command names its owner with --environment (alias --env) or --project-level, and a non-interactive run that names neither is refused rather than defaulted. (#1259)

  • The branding and flow definition editor schemas now flag what the server rejects: asset URLs that carry user:password@, typography.font_url without typography.font_family, a terminal step that also collects or acts, a step that does nothing, sso_providers without a callback transition, and a transition that sets both purpose and action. The API client's Zod schemas reject credentials in branding asset URLs, and zitadel plan measures asset URL length in bytes, as the server does. (#1224)

  • Flow definitions sync as revisions. An edited flow file plans as a revise and apply publishes a new immutable revision instead of updating in place. A schema revise re-publishes the flows pinned to it with the new user_schema in the same run. Removing a flow file no longer deletes the flow on the platform; apply fails with E_NOT_IMPLEMENTED instead. (#1116)

  • Social login gets its configuration contracts. zitadel setup now copies two more dialect files into .zitadel/meta/: idp-connection.json, the schema for a provider connection file, and sso-auth-method.json, the shape of the sso slot in a user schema. A user schema with sso.enabled: true must now list the connection slugs its users may sign in with under sso.providers, and a disabled slot must not carry the list. In a flow definition, identity_unknown is a reserved transition outcome that switches a login flow to register when a provider returns an unknown user. (#1156)

  • zitadel start now boots the local server with the platform project and a local admin, so you exist on your own server without signing up, and prints a one-time link that signs you in to the console. The new zitadel console command mints a fresh link whenever you need one; the console honours such a link only when it is served from loopback. A local zitadel setup attaches the new project to the admin's team, so zitadel claim reports it as already owned. Booting the platform project also makes it the server's default project, so the console and the hosted login now point at proj_platform rather than at the first project you create; users seeded into another project cannot sign in to them. A harness that wants a bare single-project server sets NEXTGEN_PLATFORM_BOOTSTRAP_PROJECT=false and gets neither the platform project nor the admin — including one built on @zitadel/testing, whose withZitadel boots the server by running zitadel start and so inherits the new default. Users imported with --user-file now register unique attributes from their user schema's x-unique annotations, so a user whose schema identifies users by email can sign in. (#1225)

Patch changes

  • Escape terminal control characters in everything the server returns. A value anyone can write — a user's name, a branding field, a variable — could carry ESC or OSC sequences that cleared the screen, set the window title, wrote the clipboard or disguised a link when another person ran list or get. Every response body and server error message now has its C0 and C1 control characters, DEL, and format and bidi controls shown as \xNN or \uNNNN; newlines and tabs are kept. --json output carries the same escaped text. (#1273)

  • Polish the claiming journey copy: the claim nudges in setup, status, and doctor now name the claim deadline and the data-loss stake, the claim command's help text, pre-browser message, and success output speak in claim/permanence terms, and the team id moved out of the human success output (it stays in the JSON envelope and .zitadel/secret). The setup summary's INSTALLED section now reports the SDK package the scaffold actually added and recognizes every supported framework's artifacts instead of always claiming @zitadel/sdk-next with Next.js file paths. (#1194)

  • Updated dependencies: @zitadel/server@1.0.0-alpha.23, @zitadel/api@1.0.0-alpha.23, @zitadel/config@1.0.0-alpha.23.

1.0.0-alpha.22

Minor changes

  • Clearer claim journey output: commands now sit on their own styled line in the setup box, separated from the prose so they can be copied without surrounding words, the end-of-setup notice states the concrete deadline (date, time, and zone) until which the temporary project can be claimed, terminal boxes wrap to the window width instead of breaking their frame below 80 columns, and zitadel claim explains a closed 14-day claim window instead of suggesting a futile retry. zitadel setup now also nudges against a local server when that server hosts the platform plane (probed via its runtime document), where the claim can actually complete; the offline surfaces (status, doctor) keep their cloud-only nudges. (#1147)

Patch changes

  • Updated dependencies: @zitadel/server@1.0.0-alpha.22, @zitadel/config@1.0.0-alpha.22, @zitadel/api@1.0.0-alpha.22.

1.0.0-alpha.21

Patch changes

  • zitadel claim against a CLI-launched local server (--server local) now opens the claim page on the local server itself instead of a remote default: the CLI passes the server's public base when it starts the binary or docker runtime. When a manually started loopback server still advertises a remote claim page, claim warns and names the NEXTGEN_SERVER_PUBLIC_BASE setting to fix it. (#1127)

  • Session responses identify their user through a resolved user ref derived from the user schema's own x-identifier/x-display designations (ADR 058), replacing the convention-resolved flat name/email fields this supersedes (see the earlier GET /sessions/me identity changeset). Rendering follows one chain everywhere: display, falling back to identifier, then user_id. (#1085)

    • @zitadel/server: GET /sessions/me, session get, and query sessions embed user ({user_id, identifier, identifier_property, display}), the list path hydrated with one batch resolution per page — listed sessions now carry user identity at all. The conventional attribute-name resolver (name/givenName+familyName/email) is removed.
    • @zitadel/api: the regenerated client types the new user ref component.
    • @zitadel/components: <zitadel-session>/<zitadel-logout> render from the ref; the zitadel-signout detail is now {display, identifier}; logout templates substitute {{display}}/{{identifier}} (the old {{name}}/{{email}} tokens keep filling as aliases).
    • @zitadel/sdk-core (and every SPA SDK via the shared contract): NextgenSession/ClientSession become `{userId, identifier,
  • Updated dependencies: @zitadel/server@1.0.0-alpha.21, @zitadel/api@1.0.0-alpha.21, @zitadel/config@1.0.0-alpha.21.

1.0.0-alpha.20

Patch changes

  • Scaffolded Nuxt projects no longer list @zitadel/ui-react in build.transpile. The package is gone, so writing it into a user's nuxt.config.ts left them with a config entry pointing at a dependency they do not have. (#913)

  • GET /schemas and GET /schemas/{id} now return each schema as an {id, schema, metadata} envelope carrying the full customer-authored document, and GET /schemas wraps its rows in a {schemas: [...]} object. The two read endpoints share one representation; the resource id and metadata.created_at are server-owned and can no longer collide with keys in the document. Clients that read the bare document from GET /schemas/{id} or the bare {id, created_at} array from GET /schemas must unwrap the envelope. (#930)

  • The console's schema directory now pages through GET /schemas with a Load more button, and zitadel schemas list walks every page so the printed revision history stays complete. (#947)

  • Updated dependencies: @zitadel/server@1.0.0-alpha.20, @zitadel/config@1.0.0-alpha.20, @zitadel/api@1.0.0-alpha.20.

0.1.0-alpha.19

Patch changes

  • A branding asset URL that is well-formed but unreachable no longer fails silently. logo_url / hero_url cleared every gate — the CLI's shape check and the server's save gate — published a revision, and then rendered as a 0×0 <img>: no plan output, no apply output, no console error. (#886)

    Three changes close that hole:

    • plan and apply probe each asset URL (HEAD, 2.5s budget, in parallel) and emit a non-blocking warning when it is unreachable, returns a non-2xx status, or answers with something that is not an image. Advisory by design — the machine planning is not necessarily the machine rendering the login page — so it never fails a run. Set ZITADEL_SKIP_ASSET_PROBE to turn it off (offline, air-gapped CI, a CDN that only resolves from production) and ZITADEL_ASSET_PROBE_TIMEOUT_MS to retune the per-URL budget. Only public HTTPS destinations are contacted and redirects are re-validated; loopback/private/internal targets remain inconclusive rather than becoming network requests from the machine running the plan.
    • The login UI hides an asset that fails to load and restores either the split designs' decorative placeholder or the shipped design's authored no-logo content, so a broken asset degrades to the same result as no asset instead of a blank pane or missing compact brand. Templates could not do this themselves: they are DOMPurify-sanitised and inline onerror is stripped.
    • Branding revisions can now carry plan warnings at all; previously only create/update actions could, and branding is revisioned.

    Two readability fixes ride along. A branding plan no longer dumps the whole inlined Liquid template as one escaped line: an unchanged multi-line field renders as (<n> lines, sha256:…) and a changed one as a real line diff. And the branding dialect file scaffolded into .zitadel/meta/ now spells its command mentions the way the generated app can run them (npx @zitadel/cli@<version> apply), matching the READMEs — the bare zitadel apply in the editor tooltip named a command that does not exist there.

  • Scaffolded .zitadel/** READMEs now show runnable npx @zitadel/cli@<version> … commands instead of the bare zitadel command, which does not exist inside a generated app. The branding dialect now explains that layout is the degrade preset (centered/split), not the design name — switch designs with branding eject --design. The branding README shows exactly where logo_url/hero_url go (and that custom fonts aren't configurable there yet), and the setup summary surfaces the .zitadel/ customization entry points (user schema, login flow, login template) and pairs the chosen design's wizard label with its slug (e.g. "Split (reversed)" → split-right) so you can confirm the selection applied. (#872)

  • Released Zitadel server binaries now report the published package version instead of a build timestamp, and no longer log a missing-metadata warning at startup. Source builds report the revision they were built from, and locally built Docker images identify themselves as source builds rather than claiming the published version. (#889)

  • The package documentation now matches what the packages actually do. The Next and Nuxt guides drop the removed api-base attribute in favor of configureZitadel() and the project property; the Nuxt guide documents the Nuxt module (what zitadel setup wires) with its real options and the useAuth() / useZitadelProject() composables, alongside the hand-rolled middleware path with its full option set. @zitadel/sdk-core and @zitadel/api gain real documentation of their entry points, @zitadel/config gains a package README, and the SPA guides document the ZitadelSession card and point local no-proxy experiments at the local runtime's actual default port (8080). The flow-editing guide copied into .zitadel/flows/ no longer suggests cross-flow switch/pivot transitions, which the runtime does not execute yet, and API examples use the real prefixed ID format (proj_…, team_…) instead of a retired naming scheme. (#856)

  • Drop position from x-auth-methods entries; enabled is now the only key. The user schema declares which authentication methods a user type supports. Presentation concerns such as the order methods are offered in belong to the flow engine, which takes them from the order of a step's actions in the flow definition. (#822)

    An auth-method entry now sets additionalProperties: false, matching the enclosing x-auth-methods object, which already rejects unknown method keys. A schema that still carries position fails validation instead of being accepted with the field ignored.

  • The dependency-version doctor warning now repairs with the project's own package manager and an exact-save flag (npm install --save-exact / pnpm add --save-exact / yarn add --exact / bun add --exact) instead of always suggesting a bare npm install, which would have switched managers on pnpm/yarn/bun projects and rewritten the exact pin as a caret range the check deliberately ignores. The repair command is also emitted as a structured next_commands entry, matching the agent contract's prefer-structured guidance. Widget-posture scaffolds now describe theme="auto" accurately: it follows the OS prefers-color-scheme, not the host app's own theme, and the generated comments and guidance say how to pin theme="light"/"dark" for apps that fix their scheme. (#825)

  • The Zitadel server container now starts as the non-root user it ships with. It previously created a data directory next to its own entrypoint before reading configuration, which is not writable by that user, so the container exited before serving — and setting a data directory via environment or config file did not avoid it. This also fixes zitadel start --runtime docker, which failed the same way. (#908)

  • Clarify embedded login design guidance. The generated AGENTS.md gains a "theming the widgets from your app" section (the --zl-* token bridge through the shadow DOM, the suppress-header knob, and how starter designs collapse by container width), and the Next session-state paragraph explains extending the request-boundary matcher for server-rendered header chrome. The --design flag help and the wizard hints now state what split-family designs show at card width (compact brand mark: logo_url, else hero_url; hero falls back to editable text), setup emits a warning when a widget-posture app picks split/split-right, and the scaffolded widget-posture pages mention suppress-header next to the variant/theme comment. (#863)

  • zitadel setup now pins the dev-server port in the scaffolded dev script for Next and Nuxt, so the app serves the port setup registered as the project's allowed origin. Previously a bare next dev / nuxt dev ignored that port and defaulted to 3000 — and Next silently moved to 3001 when 3000 was busy — so login rendered but the first submit failed with origin "http://localhost:3000" is not allowed for this project. The other frameworks already pinned the port in their own dev-server config (Vite's server.port + strictPort, Angular's serve.options.port) and are unchanged. An explicit port also turns a busy port into a loud EADDRINUSE instead of a silent move to a rejected origin. (#885)

    doctor verifies that dev script against the port recorded as the development issuer, so a script moved to another port is reported as an unapplied config edit and doctor --fix restores the registered port. eject now lists package.json among the edits it cannot auto-revert.

    A login that cannot start — a rejected origin being the most common cause — now reports the failure inside the login card instead of leaving a bare line of text on an otherwise empty page.

  • zitadel setup no longer claims a split design's brand pane is collapsed in your app. The pane follows the login's own container width, so the warning now describes when it actually collapses — a card-width embed, or any phone-width viewport — and why setting logo_url or hero_url in .zitadel/branding/branding.json matters there. Full-page setups get the warning too: without one of those assets, a split or split-right login loses its branding entirely at narrow widths. (#887)

  • Updated dependencies: @zitadel/server@0.1.0-alpha.19, @zitadel/config@0.1.0-alpha.19, @zitadel/api@0.1.0-alpha.19.

0.1.0-alpha.18

Minor changes

  • The framework SDK packages (react, vue, angular, svelte, solid, qwik, nuxt) now re-export the businessLocales copy overlay from @zitadel/components, so an app that only declares its framework SDK as a direct dependency can wire the work-email wording without reaching into @zitadel/components (which strict package managers would not resolve). zitadel setup --use-case business uses this to wire the overlay into every framework's generated auth pages — previously only Next scaffolds got the business copy; the SPA scaffolds pass a plain locales prop to the wrapper components, and doctor --fix regenerates the same markup from the recorded use case. (#711)

  • Add zitadel claim, which attaches a project to a team so it becomes permanent. The command mints a short-lived link with the project secret, opens it in a browser, and blocks until the developer finishes signing in there, then records claimed_at and team_id in .zitadel/secret. (#754)

    Nothing about the project changes: the issuer, users, passkeys, and applications keep working, and the project secret is not rotated. Running it again once the project belongs to a team is a clean status: "skipped" with reason: "already-claimed", whether that is known locally or learned from the platform, so agents can retry safely. Links last 10 minutes; once one lapses the command exits E_VALIDATION and points at a fresh run.

    --dry-run stops before anything is minted and reports status: "skipped", reason: "dry-run". There is nothing to preview, because a claim is decided in a browser rather than by anything the CLI computes.

    The link is always printed before any browser opens, so headless machines, SSH sessions, and --no-open need no special handling. Where a browser can be launched, BROWSER, macOS, Windows, WSL, and the usual Linux openers (xdg-open, gio open, x-www-browser, sensible-browser, gnome-open, kde-open) are all handled. --timeout <seconds> stops waiting sooner than the link's own expiry.

  • Report whether a project is attached to a team in setup, status, and doctor, so the temporary nature of a fresh project is visible without having to know zitadel claim exists. (#776)

    setup closes with an ownership line and points at claim, status carries data.project.claim (detached, or attached with the owning team_id and claimed_at), and doctor grows a claim check. All three read claimed_at/team_id from .zitadel/secret, which zitadel claim already writes, so nothing here costs a platform call and everything keeps working offline.

    A project with no team is a warning, never a failure: it works exactly like one with a team, so doctor still exits 0, and --fix deliberately does nothing because claiming needs a human in a browser. The messaging frames unattached projects as temporary without promising deletion, since nothing deletes them today.

    Nudges appear only for projects whose server in zitadel.json is the Zitadel cloud. Local and self-hosted projects have no team to attach to, so zitadel setup --server local stays quiet about it.

  • doctor now verifies the scaffolded app files. Setup records a scaffold manifest (per-file content hash and ownership class) in .zitadel/state.json; the new managed-files check fails when an infrastructure file (request boundary, custom-elements.d.ts) is missing, warns on a missing generated page, and classifies edited or user-adopted files without failing them. doctor --fix restores missing managed files and — across all checks, including the dependency repair — never replaces an existing scaffolded app file, edited or not; additive repairs (gitignore entries, env keys, the SDK dependency) stay idempotent. The check also verifies the managed config wirings (the Vite/Nuxt dev-proxy merges, Angular's angular.json proxy and auth routes) through the patchers' own idempotent transforms — a detached or missing wiring config fails, an unverifiable one warns, and --fix re-applies it. Boundary migrations converge safely: a pristine leftover middleware.ts from a Next 15→16 upgrade is swapped for proxy.ts by --fix, while an edited one becomes an explicit conflict instead of creating both. Apps scaffolded by older CLI versions are checked against template-derived expectations until doctor --fix materializes their manifest (setup skips initialized projects). (#685)

  • The CLI now enforces its supported framework floors — Next.js 15 and newer, React 18 and newer. setup refuses a below-floor app before making any change, and doctor reports one the same way: both emit an explicit E_UNSUPPORTED_PROJECT_SHAPE error naming the floor together with an upgrade hint. Only version ranges that provably cannot resolve to a supported release are rejected — protocol specs (file:, workspace:), dist-tags (latest), and ranges that admit a supported version all pass. @zitadel/sdk-next now declares the matching peer range next >=15. (#713)

  • The framework SDK wrappers now expose the widgets' surface contract: ZitadelLogin and ZitadelSession accept variant (widget | page) and theme (light | dark | auto), and ZitadelLogout accepts theme, across the React, Vue, Angular, Svelte, Qwik, and Solid wrappers. The locales prop additionally accepts partial dictionaries, so presets like businessLocales are directly assignable. Apps scaffolded by zitadel setup pin variant="page" on the generated /profile page's <zitadel-session> (keeping it full-page under the new widget-first default) and reference the SDK-shipped React JSX declarations instead of carrying a hand-maintained copy. (#686)

  • Give the embedding app a supported way to read session state for its own chrome (header navigation, account menus) — previously the widgets read GET /sessions/me internally but the host page had no documented path to the same answer and kept rendering signed-out CTAs beside a live session. (#717)

    • @zitadel/sdk-next ships a new @zitadel/sdk-next/session entry with getSession(): a client-side read of the same-origin {proxyPath}/sessions/me (the exact read <zitadel-session> performs). Works on any page — unlike server-side auth() it does not require the route to be covered by the middleware matcher — and returns the client-safe ClientAuthResult (userId/email/name, no token). 401, the backend's JSON 404, and anonymous sessions map to signed-out; other failures — including a framework's HTML 404 from a misrouted proxy — throw instead of silently rendering signed-out.
    • The client-safe auth shapes (ClientSession, ClientAuthState, ClientAuthResult) move to @zitadel/sdk-core as the single source; @zitadel/sdk-nuxt re-exports them unchanged, so its useAuth() and sdk-next's getSession() now return the identical shape.
    • CLI scaffold guidance (AGENTS.md managed section) and the generated profile pages now name each framework's session read: getSession() on Next, the auto-imported useAuth() composable on Nuxt, and the raw /__nextgen/sessions/me read for the SPA frameworks.
  • The setup wizard now asks how the login should look: keep the preselected built-in template (writes nothing), or pick one of the five starter designs (centered, split, split-right, hero, minimal) to eject it into .zitadel/branding/ and publish it as branding revision 1 during setup. --design answers the question non-interactively, as before. The chosen design is reported in the summary box, the JSON envelope (data.design, null for built-in), and the setup retry hint, and setup's next actions now point at the branding workflow — edit/plan/apply when a design was ejected, branding eject when not. (#750)

  • Tenant-customizable login templates land end to end (ADR 040): eject a design, edit real Liquid, plan/apply publishes it, and the login renders it. (#563)

    • @zitadel/server: new Branding API (POST /branding, GET /branding, GET /branding/{id}) storing immutable per-project branding revisions with a lexical template gate (size, encoding, <script>/<style>, inline handlers, javascript: URLs, | raw). Flow responses now resolve the latest revision per project instead of the hardcoded default.
    • @zitadel/api: generated client and zod schemas for the Branding API.
    • @zitadel/config: the authoritative LiquidJS template validator (@zitadel/config/template), the branding.json config dialect meta-schema, and the ejectable design catalog (centered, split, split-right, minimal) with getDefaultBrandingConfig.
    • @zitadel/components: split/minimal layout chrome for the design catalog; the {% mandatory_gates %} tag name is now single-sourced from @zitadel/config/template.
    • @zitadel/cli: .zitadel/branding/ becomes a synced resource — a branding.json descriptor plus a sibling login.liquid the CLI inlines on upload. zitadel branding eject [--design <name>] scaffolds it, zitadel setup --design <name> does so at setup and publishes revision 1, and plan/apply validate templates with the authoritative validator and publish edits as new revisions.
  • zitadel setup --use-case business now wires the SDK's businessLocales overlay into the generated Next login/register pages, restoring work-email wording on top of the widgets' neutral built-in copy (assigned via a ref so it holds on React 18, and doctor --fix regenerates the same markup from the recorded use case). The generated profile pages leave page chrome to the session card's variant="page" surface instead of hardcoding viewport height and background, every scaffolded page names the variant="widget" embedding alternative in a comment, and the scaffolded AGENTS.md guidance points at the widgets' variant/theme knobs and the SDK-shipped JSX types. (#707)

Patch changes

  • Session-state reads now bypass caches and only canonical Zitadel 401/404 error responses are treated as signed out, including expired or superseded session cookies. The browser-only getSession helper and its options type now live on the dedicated @zitadel/sdk-next/session entry instead of the package root. Framework proxies attach the project secret only to the exact POST /sessions/exchange handoff operation, so browser-reachable public and management paths no longer receive an infrastructure-supplied operator credential. After upgrading the CLI, run zitadel doctor --fix to migrate the legacy managed Vite and Angular proxy hooks. Doctor warns when an unrecognized proxy may still over-forward the project secret; custom proxy implementations remain user-owned and must be reviewed manually. (#719)

  • Add the hero landing design, a mobile compact brand header for split-family designs, split layout knobs (--zl-split-columns, --zl-split-align, --zl-split-brand-mobile), and a warn-once console signal for missing text keys. (#603)

  • stop now reaps the local server's embedded Postgres, and start self-heals a Postgres orphaned by an earlier unclean exit. The server binary starts Postgres through pg_ctl, which daemonizes the postmaster into its own session — so the process-group signal stop sends never reached it, and a crash or SIGKILL could leave it running and holding the data-directory lock. The next start then failed with E_NETWORK ("exited before becoming healthy") and a `pg_ctl: (#547)

  • zitadel setup --renderer now only advertises implemented renderers: --help lists react and calls out the planned web-component renderer as not yet available instead of offering it as an equal option. Passing --renderer web-component explicitly now fails at flag parsing — before any remote project is created — rather than mid-setup. (#681)

  • Scaffolded auth pages now derive their embedding posture from how setup met the app (ADR 044). Fresh scaffolds keep the widgets' full-page chrome; a pre-existing Next or Nuxt app gets variant="widget" cards with theme="auto" in a layout-neutral wrapper, so the login no longer paints token-colored chrome underneath the host app's own header and theme. Nuxt setup also stops writing app.vue/pages/index.vue into pre-existing apps — the shell and homepage stay user-owned, mirroring Next. The chosen posture is recorded in the scaffold manifest and doctor --fix restores it; manifests without a record restore full-page. A new dependency-version doctor check warns (with the exact install command) when an exactly-pinned @zitadel/* dependency trails the CLI's own train, so a newer CLI's guidance can't silently reference SDK entry points the app's pinned SDK does not ship yet. (#824)

  • setup no longer reorders your package.json: the dependency splice preserves the file's key order, indentation, line endings, and trailing newline (only the touched dependency map is name-sorted, as package managers write it); the Angular dev-script merge behaves the same. Setup's file reporting is also cleaned up — files_written now lists deduplicated file paths only (directories and double-counted env merges are gone), and a new data.files carries one typed row per artifact ({path, kind, action}) so scripts can tell what setup created versus merged into. (#688)

  • zitadel setup now asks "Who will sign in to your app?" and scaffolds the matching schema fields: minimal (email only), consumer (email, given and family name), or business (adds a companyName attribute). minimal is the default, so the no-flag scaffold now collects email only — a deliberate slim-down from today's output: given/family name move to consumer/business, and dateOfBirth is no longer scaffolded by any use case. The default schema and login-flow templates (embedded as the server-side fallback for projects created without the CLI) are slimmed to the same email-only baseline, so the no-CLI default and the minimal use case now agree; the per-field bodies for givenName/familyName/companyName move into the config field catalog the CLI composes from. This is a second axis alongside the sign-in preset (#448): the use case owns the schema field set, the sign-in preset owns the flow, and the login flow's register step is derived from the chosen fields instead of a hard-coded list — so the two compose instead of multiplying into a bundle per pair. The question is asked before the sign-in preset; non-interactive and scripted runs use --use-case (defaults to minimal, never blocks); the choice is recorded in zitadel.json for guidance/status only, never behavior. business is a field set only for now — companyName is a plain user attribute with no org/team model behind it yet. Every (use case × sign-in preset) pair is hygiene-tested against the flow validator. The unused, divergent buildUserSchema/fieldPreset helpers are removed in favor of a single source of field defaults. (#551)

  • Flip <zitadel-login> to widget-first: the default variant="widget" is content-sized, transparent through every layer, injects no default font into the host document, and never steals focus on load — the embedding app owns the page. Dedicated login routes (hosted shell, scaffolded pages) opt into the previous full-page behavior with variant="page". Split-family responsive chrome now keys off the widget's own width via container queries (baseline 2023 browsers), the hero design ships neutral placeholder copy instead of fabricated claims, and split tenants with only a hero_url keep a compact banner fallback on narrow widths. (#603)

  • Updated dependencies: @zitadel/server@0.1.0-alpha.18, @zitadel/api@0.1.0-alpha.18, @zitadel/config@0.1.0-alpha.18.

0.1.0-alpha.17

Patch changes

  • Fixes from alpha.16 community feedback: (#544)

    • Custom schema fields now render a readable label. A property with no catalog entry (e.g. department, dateOfBirth) falls back to a humanised name ("Department", "Date of birth") on the form instead of leaking the raw <step>.field.<name> text key. A catalogued key still wins, so localised labels are unaffected.
    • The scaffolded .zitadel/flows/README.md no longer contains the "Presets" section twice.
    • The warn/default-flow-swap plan warning now leads with the impact in plain language: the new flow becomes the default for its purposes, and every page that does not explicitly set flow-name on <zitadel-login> will start rendering it — scope it via audience or pin flow-name to opt out.
    • The flip-table validation error (login/register entry step missing its user_not_found/user_already_exists transition) now explains who gets stuck where: someone without an account would be stuck at sign-in instead of being routed to registration, and vice versa. Plan, apply, and the server report the same wording.
  • plan --json and apply --json now enumerate what they touch: a data.changes array with one {kind, action, file, id?, previous_id?} row per resource (action ∈ create/update/revision/delete). Plan rows preview the pending sync; apply rows report what happened, carrying the resulting platform ids (created ids, newly published revision ids), so agents can verify an edit did what they intended without re-applying. apply also gains next_actions/next_commands ("changes are live" + a versioned plan follow-up), and schemas list emits created_at in snake_case like every other envelope field. Counters and files_updated (local write-backs only) are unchanged. (#543)

  • The interactive setup wizard now detects a running local Zitadel server the same way start and doctor do — via the runtime metadata written by zitadel start plus a /healthz probe on the default port — and preselects it in the server choice. Previously it scanned localhost ports for an OIDC discovery document the server does not serve, so it always reported "No local OIDC servers found" even with a healthy server running. (#546)

  • Updated dependencies: @zitadel/config@0.1.0-alpha.17, @zitadel/server@0.1.0-alpha.17, @zitadel/api@0.1.0-alpha.17.

0.1.0-alpha.16

Patch changes

  • Select a flow definition by name. <zitadel-login> gains a flow-name attribute (flowName prop on every framework wrapper) that sends flow_definition_name on flow start, so a project with several synced flows can run a specific one instead of the audience-resolved default. An unknown name or a purpose mismatch surfaces as a clear startup error naming the attribute. Audience selection itself is now honored and deterministic: hinted app beats hinted team beats the newest unscoped flow, and a flow scoped to an app/team no longer captures the project default. The flows README and plan/apply docs explain how to add and select a second flow. (#514)

    Because newest-unscoped-wins means a new flow can silently take over the default login, plan warns on any create of an active, unscoped flow in a project that already has flows (warn/default-flow-swap, a non-blocking # warning: line and a --json warnings entry) — scope the flow via audience or pin flow-name in the widget to opt out. The offline dialect gains the committed auth-methods/auth-method meta-schema copies that user-schema.json references, so editors resolve the full dialect without network access.

  • Disabling passkey in the user schema (x-auth-methods.passkey.enabled: false) is now enforced for flows. A flow step declaring a passkey or passkey_register action against a schema that does not enable passkey fails validation at plan time (and on the server at apply time) with `step "…": action "…" offers passkey but "passkey" is not an enabled (#515)

  • The passkey origin-allowlist rejection now names the allowed origins (e.g. origin "http://127.0.0.1:3000" is not allowed for this project (allowed: http://localhost:3000)), and <zitadel-login> surfaces the server's error message instead of a generic "returned 400". @zitadel/api exports the new apiErrorMessage helper for extracting the server error envelope from an ApiError. (#497)

  • plan and apply now validate flow definitions against the same rules the server enforces — before any mutation. A flow missing an invariant (e.g. a login entry step without user_not_found -> register while register is a wired purpose) fails at plan time with the server's exact wording instead of half-applied after the schema already revised. Errors aggregate across flows (--json carries structured details.issues); product guidance surfaces as non-blocking # warning: lines in the plan. The validator ships as @zitadel/config/validate. Escape hatch: set ZITADEL_SKIP_FLOW_VALIDATION to skip the pre-flight if it ever disagrees with your server version. (#499)

  • Remove the scaffold landing chooser ("Sign in, create an account, or open your profile") from every framework template. Fresh apps now redirect / to /login, setup next steps tell users to open /login, and Next.js auth pages no longer duplicate login/register links the widget already provides in-flow. (#517)

  • Scaffolded projects now explain their own next step. zitadel setup writes an AGENTS.md guidance section for AI agents and an "Authentication (Zitadel)" section into the app README (marker-fenced — existing content is never clobbered), copies the flow/schema dialect meta-schemas into .zitadel/meta/, and scaffolds flow files with "$schema": "../meta/flow-definition.json" so editors validate and autocomplete flow edits offline. The $schema pointer is local-only: sync ignores it and write-back preserves it. ZitadelLogin wrappers gain typed locales/lang props for labelling custom flow steps (see the new "Customize copy" docs page). (#502)

    zitadel eject removes what setup wrote: the marker-fenced guidance section is stripped from README.md/AGENTS.md (content outside the markers is untouched), and a file is deleted only when nothing but the scaffold-created header would remain — no stale golden path survives pointing at deleted .zitadel/ files.

    Every SDK wrapper now forwards locales/lang to the widget (previously only React did; Solid/Qwik/Svelte accepted and discarded them, Vue/Angular did not expose them). The flow dialect meta-schema (@zitadel/server embeds it; @zitadel/config ships the committed copy) marks a transition's action as nullable, matching the OpenAPI contract — editors no longer flag "action": null.

  • zitadel setup now asks "How should users sign in?" and scaffolds the matching schema+flow preset: password-first (today's default) or passkey-first (a one-tap passkey on the login entry step with an email → password fallback path, passkey-primary registration, and email kept required so the fallback always works). Non-interactive and scripted runs use --preset; the choice is recorded in zitadel.json. Presets are named bundles under @zitadel/config (the mechanism behind app-type selection, #448) and are hygiene-tested: every bundle must pass the flow validator and resolve every text key in every builtin locale. (#500)

  • Setup failure guidance now reconstructs the full invocation: retry hints and next_commands carry --preset, --renderer, --dev-port, and --non-interactive alongside --framework, so following the printed command verbatim reproduces the requested scaffold instead of silently falling back to defaults. HTTP 404s map to the new E_NOT_FOUND error code with exit code 4 (previously E_VALIDATION/exit 3 — update scripts that branch on it); a 404 without the platform's error envelope also names the URL and asks whether the target is a Zitadel platform API. Passkey-first scaffolds add a note to AGENTS.md telling agents to verify the login loop via the email/password fallback or a CDP WebAuthn virtual authenticator, since automated browsers cannot complete passkey ceremonies. (#524)

  • Setup and status guidance now tracks where you are in the journey. The zitadel setup terminal box ends on the verify mission (install, start, register → sign out → sign in) plus a single breadcrumb to zitadel status and the README's Zitadel section, instead of listing customize/publish steps before the first login. The --json envelope keeps the complete next_actions/next_commands for agents. zitadel status asks the platform whether the project has users yet: none → verify-login guidance, some → the customize (.zitadel/schemas/, .zitadel/flows/) and plan/apply publish steps; when the server is unreachable it keeps the lifecycle-only output. next_commands is staged in lockstep: before the first proven login it offers plan and withholds apply. (#516)

    The server implements GET /users (previously generated-but-unimplemented, returning 500): bearer-scoped to the token's project — the exact call shape of the status probe — returning attribute-hydrated users with a stable creation-ordered offset/limit window (spec defaults limit 20, max 100). The staged status therefore works against a real runtime, not only the api-mock.

  • Updated dependencies: @zitadel/server@0.1.0-alpha.16, @zitadel/config@0.1.0-alpha.16, @zitadel/api@0.1.0-alpha.16.

0.1.0-alpha.15

Patch changes

  • apply now re-pins flows to a freshly published schema revision in the same run: the CLI rewrites user_schema in every local flow file pinned to the superseded revision (lockfile-style, announced by plan and reported in the output) and the flow update carries the new id — editing a schema and using the new field in a flow no longer fails validation or needs a second apply. Interrupted runs recover via a previousId marker in .zitadel/state.json. (#485)

  • Make plan diffs trustworthy and keep local config in lockstep with live state. @zitadel/config/normalize is the shared canonical-form normalizer (drops the server's empty audience echo and spelled-out x-* meta-schema property defaults); the sync engine hashes and diffs in normalized form (with a legacy-hash fallback so existing state files don't read as edits), and setup/apply write the server's canonical body back to the local file — reported in human and --json output — so a one-field edit renders as a one-field diff and applying can no longer silently strip live settings. The api-mock now mirrors the server's unconditional audience echo. (#485)

  • Surface the customize loop after setup: the "Zitadel is ready" next steps now point at the editable .zitadel/schemas/ and .zitadel/flows/ files and the plan/apply commands, and the scaffolded READMEs are restructured workflow-first (mental model → example → making changes → common changes). (#485)

  • Updated dependencies: @zitadel/config@0.1.0-alpha.15, @zitadel/server@0.1.0-alpha.15, @zitadel/api@0.1.0-alpha.15.

0.1.0-alpha.14

Minor changes

  • Scaffold editable schema and flow config from shared local defaults, add project default seeding control, and seed sync state so plan is idempotent immediately after setup. (#341)

  • Add a post-sign-in <zitadel-session> "signed in as" card: a dedicated element exposed through every SPA SDK and re-exported from sdk-next. CLI scaffolds now render it as the post-sign-in /profile page (with a Logout action) across all frameworks. Identity is read from GET /sessions/me, preferring name then email then user_id. (#443)

    <zitadel-logout> now sources its identity from the same getMySession operation instead of the __nextgen_display cookie, so both signed-in surfaces work against the real backend. Both components route their getMySession/revokeMySession calls through the shared api-client wrappers that enforce credentials: "include".

Patch changes

  • Route published CLI telemetry to the production Mixpanel project during npm publish. (#469)

  • Map CLI telemetry events to Mixpanel's event country property so country appears correctly in analytics. (#436)

  • Verify public npm provenance after repository publication. (#474)

  • Updated dependencies: @zitadel/server@0.1.0-alpha.14, @zitadel/api@0.1.0-alpha.14, @zitadel/config@0.1.0-alpha.14.

0.1.0-alpha.13

Patch changes

  • Drop the x-password user-property annotation. The flow engine sources the password challenge from the reserved x-auth-methods#password field name combined with x-auth-methods.password.enabled at the schema root (introduced in #400); x-password is no longer read by any code path. Removed from the user-property.json meta-schema and the CLI's generated password preset; comments and docs updated to match. (#411)

  • Updated dependencies: @zitadel/server@0.1.0-alpha.13, @zitadel/api@0.1.0-alpha.13.

0.1.0-alpha.12

Minor changes

  • Add opt-out anonymous usage telemetry. Disable with --no-telemetry, DO_NOT_TRACK=1, or ZITADEL_TELEMETRY=0. (#392)

Patch changes

  • Scaffolded app pages now enforce the dark surface the Zitadel widgets are designed for (color-scheme: dark, #0f0f11), instead of following the OS light/dark setting — across every framework template (next, react, vue, angular, nuxt, solid, svelte, qwik). This fixes the inconsistency where the <zitadel-logout> avatar (and other non-widget chrome, e.g. the /profile view) rendered on a white background while <zitadel-login> enforced its own dark surface. (#337)

    Removed misleading field hints from the login component locales (en, de, it): the password "include a symbol and number" hint (only minLength is enforced server-side) and the YYYY-MM-DD date-of-birth hint (native <input type="date"> localizes its own format and submits ISO). A dynamic, validation-rule-driven hint is tracked in #251.

  • Set the now-required kind on the default flow's step actions: submit actions use submit, the register/login routing actions use navigate. Without it, the generated flow fails validation against the updated flow-definition schema. (#372)

  • Align the CLI flow-definition sync update with the PUT contract: send the { flow_definition } body envelope and the required project_id query parameter so updates to existing flow definitions are accepted by the generated server. (#354)

  • Updated dependencies: @zitadel/api@0.1.0-alpha.12, @zitadel/server@0.1.0-alpha.12.

0.1.0-alpha.11

Patch changes

  • CLI scaffolds now write the project service-key secret to .env.local as ZITADEL_PROJECT_SECRET, and the React/Vue/Angular dev proxies plus the Next.js and Nuxt server middlewares send it as the bearer on every proxied request instead of synthesising sk_<project_id> from the public project id. The secret stays server-side: .env.local is gitignored, Vite only exposes VITE_-prefixed vars to the client, Next.js auto-loads .env.local into process.env server-side, and the Nuxt module reads process.env.ZITADEL_PROJECT_SECRET in its setup() and pushes it into Nuxt's server-only runtimeConfig.nextgen.projectSecret (overridable at deploy time via NUXT_NEXTGEN_PROJECT_SECRET). (#310)

    Also drops the unused onExchangeResponse hook from NextgenMiddlewareOptions (no callers anywhere; alpha so no external usage to break).

  • Updated dependencies: @zitadel/server@0.1.0-alpha.11, @zitadel/api@0.1.0-alpha.11.

0.1.0-alpha.10

Patch changes

  • Improve fresh-app CLI recovery guidance and align agent automation hook docs with the rendered login controls. (#328)

  • Updated dependencies: @zitadel/server@0.1.0-alpha.10, @zitadel/api@0.1.0-alpha.10.

0.1.0-alpha.9

Patch changes

  • Report local port conflicts clearly, sweep managed local runtime orphans with stop --all, and explain non-empty setup targets. (#325)

  • Updated dependencies: @zitadel/server@0.1.0-alpha.9, @zitadel/api@0.1.0-alpha.9.

0.1.0-alpha.8

Patch changes

  • Updated dependencies: @zitadel/server@0.1.0-alpha.8, @zitadel/api@0.1.0-alpha.8.

0.1.0-alpha.7

Patch changes

  • Updated dependencies: @zitadel/server@0.1.0-alpha.7, @zitadel/api@0.1.0-alpha.7.

0.1.0-alpha.6

Patch changes

  • Step fields and actions are now ordered [{ name, ... }] arrays on the wire (ADR 021). Templates iterate them in authorial order; the orchestrator builds fields_by_name / actions_by_name views for keyed lookups. The private @zitadel/api-mock workspace follows the same wire shape for tests. gates stays a name-keyed object for now. (#270)

  • Improve local bootstrap guidance for Docker prerequisites, generated auth pages, and agent browser proof. (#303)

  • Updated dependencies: @zitadel/server@0.1.0-alpha.6, @zitadel/api@0.1.0-alpha.6.

0.1.0-alpha.5

Minor changes

  • Add setup --framework react|vue|angular|nuxt support to the CLI. Each framework scaffolds its auth entry/pages and wires /__nextgen/* calls to the backend with a sk_<project_id> bearer attached: React and Vue get a dev proxy magicast-merged into the Vite config (vite.config.*) that reads the project id from ZITADEL_PROJECT_ID; Angular gets a proxy.conf.cjs wired into angular.json that reads it from zitadel.json; and Nuxt registers the @zitadel/sdk-nuxt module in the Nuxt config (nuxt.config.*), which adds the proxy via server middleware. A --dev-port flag sets the scaffolded dev-server port. (#257)

  • Make the generated Next.js auth app easier for agents and developers to prove end-to-end registration, logout, and login in a visible browser. (#299)

Patch changes

  • Allow fresh app scaffolding after zitadel start creates local runtime ignore files, and load Nuxt runtime config through the Nuxt virtual imports module. (#295)

  • Updated dependencies: @zitadel/api@0.1.0-alpha.5.

0.1.0-alpha.4

Patch changes

  • Harden local setup guidance, Next 16 scaffolding, and login form automation. (#279)

  • Updated dependencies: @zitadel/api@0.1.0-alpha.4.

0.1.0-alpha.3

Patch changes

  • Allow same-directory setup after starting the local Zitadel runtime. (#272)

  • Updated dependencies: @zitadel/api@0.1.0-alpha.3.

0.1.0-alpha.2

Patch changes

  • Derive alpha local runtime images from the installed CLI version, pin generated SDK dependencies to the same alpha train, and emit exact-version follow-up commands for reproducible tester reports. (#265)

  • Make doctor local-runtime checks advisory for cloud setup, harden fresh Next.js scaffolding, auto-install setup dependencies, normalize public follow-up commands, and avoid assuming Next.js in local-runtime setup guidance. (#255)

  • Updated dependencies: @zitadel/api@0.1.0-alpha.2.

0.1.0-alpha.1

Minor changes

  • Add top-level zitadel commands for managing a Docker-backed local Zitadel runtime. (#245)

Patch changes

  • Scaffold prerelease SDK dependencies on the same npm dist-tag as the CLI. (#228)

0.1.0-alpha.0

Minor changes

  • Drop unused auth methods from the zitadel setup prompt and consolidate flow domain logic into apps/cli/src/lib/flows/. The setup prompt previously offered passkey, password, and totp as a multiselect, but totp is not a valid key under x-auth-methods per the OAS spec (only password|passkey|magic_link|sso|otp are allowed with additionalProperties: false), so any user schema written with totp selected failed validation. The Go flow engine only wires password and identifier challenges today; passkey has a defined JSON shape but no runtime handler yet. (#158)

    Breaking change for non-interactive callers. The --auth-methods flag (CSV) has been renamed to --auth-method (single value); allowed values are passkey (default) or password. Agents and scripts that previously passed --auth-methods password must update to --auth-method password.

    Internally, the flow_definition shape (Zod schema, types, build, read/write, text-key extraction) now lives behind a single apps/cli/src/lib/flows/ module exported through one barrel. The sync layer remains shape-agnostic and treats flow payloads as opaque bytes. A follow-up PR will introduce apps/cli/src/lib/user-schema/ mirroring the same layout.

  • Remove the pre-claim / claim lifecycle from the CLI and api-mock. The zitadel claim and zitadel claim status commands, the ClaimClient interface, the InitClaim* / ClaimStatus* schemas, the claimed_at / team_id fields on .zitadel/secret, the E_CLAIM_REQUIRED and E_PLATFORM_HANDOFF error codes, the production-claim gates in apply and deploy connect, and the api-mock's claim/init / claim/status handlers and completeMockClaim() export are all gone. The SDK's resolveZitadelRuntime production-issuer error message no longer references the removed zitadel claim command. /projects/{id}/claim/init and /projects/{id}/claim/status are not in the OpenAPI spec and have no backend; the surface only worked against the mock. (#150)

  • zitadel setup now scaffolds a middleware.ts at the project root that wires up nextgenMiddleware from @zitadel/sdk-next. The middleware forwards /__nextgen/* same-origin to NEXTGEN_ISSUER_URL (the auth backend) and gates /profile behind a JWT check. (#157)

    The file uses the middleware.ts + function middleware() convention because Next 15 only recognises that form; Next 16 accepts it too (the proxy.ts rename is deprecated-but-backwards-compatible). Using the universal form means one template works on every supported Next major.

    Scaffolded pages now use api-base="/__nextgen" instead of pointing at the backend URL directly, so no CORS configuration is needed and the backend URL never reaches the browser bundle. .env.local no longer writes NEXT_PUBLIC_ZITADEL_API_BASE; it writes NEXTGEN_ISSUER_URL (the same value, server-side only). doctor --fix re-applies middleware.ts if missing.

  • zitadel setup no longer scaffolds or uploads the user schema and flow definition — the Zitadel server now provisions these defaults when a project is created. Setup no longer writes .zitadel/schemas/user.json or .zitadel/flows/default.json, runs no sync step at the end, and the --no-apply flag (which only gated that sync) has been removed. The sync engine and the hidden apply/plan commands remain in place for a future pull-based workflow. (#208)

    Behavior change for non-interactive callers. zitadel setup --no-apply is no longer a valid flag and will error; remove it from scripts and agents.

    Scaffolded Next.js login/register/profile pages now configure the SDK via configureZitadel(...) and pass the resulting project handle to the <zitadel-login> / <zitadel-logout> web components through the project prop, instead of the removed api-base / project-id attributes. To support an app that declares only @zitadel/sdk-next as a direct dependency, @zitadel/sdk-next/client now re-exports configureZitadel and getApi.

  • Rename the public packages to the @zitadel scope and publish them to npm via changesets with GitHub OIDC trusted publishing. This is the first @zitadel/*-scoped release line, cut as an alpha prerelease. (#209)

Patch changes

  • Document the default fresh-app credential journey and refine the component copy and password autocomplete behavior for registration flows. (#206)

  • Adopt the CLI to the Nx lint and strict TypeScript CI checks. (#11)

  • Updated dependencies: @zitadel/api@0.1.0-alpha.0.

Was this helpful?