---
title: SDK changelog
description: What changed in each SDK release, newest first, and anything you need to do when upgrading.
---

Every release is backwards compatible unless an entry says otherwise
under **Action needed**. New features are opt-in: upgrading doesn't add
anything to your screens that you didn't ask for. When a fix changes
how something looks or behaves, it's listed under **Fixes you will
notice**, so nothing changes without warning.

## Unreleased

`@zevauth/react`, `@zevauth/nextjs`, `@zevauth/js`, `@zevauth/backend`, and
two new packages: `@zevauth/ui` and `@zevauth/vue`.

> **Note:**
> No action needed. Upgrade with `npm install @zevauth/react@latest`
> (or `@zevauth/nextjs@latest`). If you use `@zevauth/nextjs`, it needs
> this release of `@zevauth/react`. npm installs it for you.

### New

- **Python** (`zevauth` on PyPI, new). Session verification, `get_auth`
  and `require_auth` (sync and async), the backend API client with sync
  and async clients and page iterators, webhooks and the session bridge,
  with integrations for [FastAPI](/sdk/fastapi/), [Django](/sdk/django/)
  and [Flask](/sdk/flask/). See [Python](/sdk/python/).

- **Go** (`github.com/zevop/zevauth-go`, new). Session verification,
  `GetAuth` and `RequireAuth`, `net/http` middleware, the backend API
  client with a page iterator, webhooks and the session bridge, using the
  standard library only. See [Go](/sdk/go/).

- **Java and Spring Boot** (`com.zevauth:zevauth-java` and
  `com.zevauth:zevauth-spring-boot-starter`, new). Session verification,
  the backend API client, webhooks and the session bridge for any JVM
  server, with Jakarta Servlet adapters. The starter auto-configures
  everything from `zevauth.*` properties and adds `@RequireAuth`, an
  `Auth` controller argument and Spring Security integration. See
  [Java](/sdk/java/) and [Spring Boot](/sdk/spring/).

- **PHP and Laravel** (`zevauth/zevauth-php`, new). Session verification,
  `getAuth` and `requireAuth`, the backend API client, webhooks and the
  session bridge for any PHP app, plus a Laravel integration: the `zevauth`
  middleware and guard, Blade directives, bridge routes and a webhook
  route helper. See [PHP](/sdk/php/) and [Laravel](/sdk/laravel/).

- **Ruby and Rails** (`zevauth` gem, new). Session verification,
  `get_auth` and `require_auth`, the backend API client with `each`
  pagination, webhooks and the session bridge for any Ruby app, plus Rack
  middleware and a Rails engine: `require_zevauth!`, view helpers, bridge
  routes and `zevauth_webhook_event`. See [Ruby](/sdk/ruby/) and
  [Rails](/sdk/rails/).

- **Server SDK contract and conformance fixtures.** Every ZevAuth server
  SDK now follows one written contract and runs one shared set of test
  tokens, webhook signatures and API errors, so every language behaves
  the same way. See [Server SDKs](/concepts/server-sdks/).
- **Session bridge for server-rendered apps.** A new `sessionBridge: { url }`
  option in the browser SDK, the React provider, `@zevauth/ui` and Vue
  posts each new access token to your server, which keeps it in an
  HttpOnly cookie. `@zevauth/backend/bridge` provides the endpoint, the
  cookie reading and the handshake page. Off unless you set it. See
  [Server-rendered apps](/concepts/session-bridge/).
- **Webhook verification in Node.** `verifyWebhook` and
  `constructWebhookEvent` from `@zevauth/backend` (also
  `@zevauth/backend/webhooks`).
- **`users.resetMfa()` and `users.iterate()`** in `@zevauth/backend`. Errors
  now carry `requestId`, and 429 responses honour `Retry-After`.
- **Stricter token checks in `@zevauth/backend/verify`.** Only RS256 is
  accepted, 5 seconds of clock difference are allowed, and refusals carry a
  code (`token_expired`, `invalid_audience`…) through `ZevAuthVerifyError`.

- **Themes.** `appearance.theme` takes `'soft'`, `'minimal'`, `'sharp'`,
  `'contrast'` or `'default'`. You can override any value on top of a theme.
  See [Themes](/concepts/appearance/#themes).
- **Design tokens.** `appearance.variables` sets colours, font, base
  size, radius, card shadow and card width. Every colour can take a
  `{ light, dark }` pair. See [Variables](/concepts/appearance/#variables).
- **Per-element styling.** `appearance.elements` takes a class name or a
  style object for any of 43 named parts. See
  [Elements](/concepts/appearance/#elements).
- **Cascade layers.** `appearance.cssLayerName` puts the component CSS in
  a layer, so your utility classes win without `!important`.
- **`appearance` on every component.** `<UserButton>`,
  `<UserProfile>`, `<OrganizationSwitcher>`, `<OrganizationProfile>` and
  `<SignInWithZevId>` now take it, as `<SignIn>` and `<SignUp>` already
  did.
- **Your own wording.** A `localization` prop on the provider and on
  each component changes any label, button or message, or translates
  them all. Errors from the API can be replaced by code. See
  [Customising the components](/concepts/appearance/).
- **Server-rendered styles.** `getComponentStyles()` comes from
  `@zevauth/react/styles` and `@zevauth/nextjs/server`. Use it to put the
  stylesheet in your server HTML without importing React.
- **New exports.** `Appearance` and its part types, `themes`, `ThemeName`,
  `Localization`, `defaultLocalization`. `@zevauth/nextjs` now also
  re-exports `ImageField` and `useZevTheme`.
- **Organization and session methods** in `@zevauth/js`:
  - `organizations.create()`, `organizations.leave()` and
    `organizations.delete()`.
  - `me.sessions()`, `me.revokeSession()` and `me.revokeOtherSessions()`.

  Who can create or delete organizations is still controlled in the
  console.

- **New components.** `<CreateOrganization>`, `<OrganizationList>` (a
  "Choose an account" screen) and `<UserAvatar>`. See
  [Components](/sdk/components/).
- **Organization policy in the browser.** `client.organizationSettings`
  reports whether end users may create organizations, whether slugs are
  on, and whether deleting from the app is allowed. `<CreateOrganization>`
  shows the slug field when your environment uses slugs, and the create
  and delete options hide themselves when the console turns them off.
- **IP addresses in the device list are your choice.** Under
  **Authentication → Sessions** in the console: hidden (the default),
  approximate (the network only), or full. The API applies it to
  `GET /v1/me/sessions`, and `<UserProfile showSessions />` labels
  approximate addresses as such.
- **New options on existing components.** All are off by default, so
  your screens don't change until you turn one on:
  - `<UserProfile>`: `variant="sidebar"`, and `showSessions` to list the
    person's signed-in devices with a way to sign them out.
  - `<OrganizationProfile>`: `variant="sidebar"`, plus `showLeave` and
    `showDelete`. Delete asks for the name to be typed first.
  - `<OrganizationSwitcher>`: `createOrganizationMode` adds "Create
    organization".
  - `<UserButton>`: `userProfileMode` adds "Manage account", which opens
    the profile in a dialog or goes to your own page.
- **`@zevauth/expo`** (new). Native sign-in for iOS and Android: the same hooks
  as `@zevauth/react`, native screens (sign-in, sign-up, two-step, account,
  profile with devices), the session in the Keychain or Keystore, and Sign in
  with ZevID through the system browser with PKCE. Themes, variables, elements
  and localization use the web's vocabulary. See [Expo and React Native](/sdk/expo/).
- **Native app support in `@zevauth/js`.** All additive; the web behaves as
  before:
  - an `asyncStorage` option for asynchronous stores;
  - `handleIncomingUrl()` to finish a link the app was opened with;
  - `createZevIdAuthorizationUrl()` for the system browser sign-in;
  - `revalidate()` for when the app returns to the foreground.

  `@zevauth/react` adds a DOM-free `@zevauth/react/headless` entry.
- **`@zevauth/ui`** (new). Mount the components into any page, with no
  framework: `mountSignIn(element)` and friends, or a single script tag
  that sets `window.ZevAuthUI`. See [Any framework](/sdk/ui/).
- **`@zevauth/vue`** (new). A Vue 3 plugin, composables with the same
  fields as the React hooks, the control components, and the UI
  components. Includes Nuxt usage. See [Vue](/sdk/vue/).
- **Verifying tokens on any server.** `@zevauth/backend/verify` exports
  `verifyZevAuthToken`, `getAuth` and `requireAuth` for any server that
  uses the standard `Request`. There are guides for
  [React Router](/sdk/react-router/),
  [TanStack Start](/sdk/tanstack-start/) and [Astro](/sdk/astro/). The
  main `@zevauth/backend` export is unchanged.
- **Two-step verification.** People can protect their account with an
  authenticator app and recovery codes. It's set per environment in the
  console: off, optional (the default) or required. Nothing changes for
  anyone until they set it up or you choose required. See
  [Two-step verification](/concepts/mfa/).
  - `<SignIn>` and `<SignUp>` show the code step when a sign-in needs
    one, including after a magic link or ZevID.
  - Under required, `<SignIn>` walks people through setup after they
    sign in. The new `<TaskSetupMfa>` catches anyone who arrives with
    setup still to do.
  - `<UserProfile>` shows a two-step section under Security when your
    app requires it or the person has it. `showMfa` controls it.
  - Options: remember devices for up to 90 days, trust ZevID's own
    two-step, and choose whether organization admins can require it.
  - `@zevauth/js`: `signIn.verifySecondFactor()`, `client.secondFactor`,
    `client.pendingTasks`, `client.mfaSettings`, `cancelSecondFactor()`
    and `client.me.mfa`.
  - `@zevauth/backend/verify` and `@zevauth/nextjs/server`: `requireAuth`
    refuses sessions with setup still to do, and `{ requireMfa: true }`
    demands a second factor for one route.
- **Native apps.** "Sign in with ZevID" from iOS and Android, following
  the OAuth standard for native apps:
  - the sign-in can be bound to your app with PKCE and a `state`;
  - a production sign-in returns only to a universal link or app link
    your domain has verified;
  - register your apps under **Project settings → Mobile apps**, which
    generates the association files to publish and shows each app's
    status;
  - verified apps are named on the "Sign in with ZevID" consent screen
    with the device, such as "Acme on iPhone".

  See [Native apps](/concepts/native-apps/).
- **PKCE for ZevID sign-in on the web.**
  `client.signIn.withZevId({ pkce: true })` binds the sign-in to the tab
  that started it and checks the `state` that comes back. Without
  `pkce`, nothing changes.
- **ZevID sessions follow ZevID.** A session that started with "Sign in
  with ZevID" ends when the person signs out of ZevID everywhere,
  changes their ZevID password or has their ZevID account closed, as it
  already did when they removed your app. Signing out of one ZevID
  session ends only the sessions made from it. Sessions are also checked
  against ZevID at least daily.
- **Trusting ZevID's two-step works.** With "Trust ZevID's two-step" on,
  a ZevID sign-in that combined a password with a second step, within
  the last few minutes, skips your own code. An email code alone does
  not count.

  It also needs a fresh ZevID sign-in after 90 days, or after 30 days
  unused. You can shorten both in the console. When that happens,
  `<SignIn>` explains why the person was signed out.

### Fixes you will notice

- **Next.js server verification is stricter.** `@zevauth/nextjs/server`
  now uses the same verifier as every ZevAuth server library: RS256 only,
  five seconds of clock tolerance, and a cooldown on refetching keys for a
  token that names an unknown key. Valid tokens are accepted exactly as
  before. Errors from `verifyZevAuthToken` are now a typed
  `ZevAuthVerifyError` with a `code` (`token_expired`,
  `invalid_audience`, …), exported from `@zevauth/nextjs/server`. Code that
  only checked whether verification threw keeps working.

- **Sign-up no longer stops after the confirmation code.** In environments
  that ask for an email code before giving access, `<SignUp>` confirmed
  the code and then stayed on the same screen with no session. It now
  signs the person in with the password they just chose, including the
  two-step step if they need one.

These fix behaviour that didn't match the documentation. If you relied
on the old behaviour, here's what changes.

- **Sign in with ZevID now returns to the redirect you asked for, not
  always the first registered one.** An app with several registered
  callbacks could land on the wrong one after a ZevID sign-in.
- **A failed ZevID sign-in returns your own `state`.** It used to carry
  an internal value instead, which your app couldn't match. If you
  didn't send a `state`, none comes back.

- **`accentColor` set in code now paints.** Before, an `accentColor` in
  `appearance` changed nothing and the console accent showed instead.
  If you set one and want to keep the console colour, remove it.
- **The provider's `appearance` now reaches every component.**
  `<UserButton>`, `<UserProfile>`, `<OrganizationSwitcher>`,
  `<OrganizationProfile>` and `<SignInWithZevId>` used to ignore it. If
  you set `appearance` on the provider, these now match your other
  components.
- **`signUpTitle` and `signUpSubtitle` set in code are now used.** Before,
  `<SignUp>` always printed the console's heading.
- **No more double focus rings.** If your site puts a global outline on
  `:focus-visible`, it no longer draws a second ring around our fields
  and buttons. They keep their own focus ring.
- **`load()` runs once even when called twice at the same time.** A
  second call while the first was still running used to start a second
  load. That second load could try to spend a sign-in link or handoff
  code the first had already used, and the person saw a sign-in error.
  Concurrent calls now share one load.
- **Avatar initials and profile spacing hold up against site styles.**
  Some site stylesheets made the initials in `<UserButton>` invisible, or
  put a large gap between `<UserProfile>` sections. Both are now
  protected from outside styles.
- **Profile picture buttons size to their text.** Replace and Remove in
  `<UserProfile>`, `<OrganizationProfile>` and `<ImageField>` were
  stretching to full width against their intended design.

### Changed under the hood

- **Sign-in methods now return a result.** `signIn.withPassword()`,
  `verifyMagicLink()` and `verifyEmailCode()` resolve to
  `{ status: 'complete' }` or `{ status: 'needs_second_factor', … }`
  instead of nothing. Code that ignores the result keeps working. Code
  that assigns these calls somewhere typed as `() => Promise<void>` may
  need its type widened to `Promise<unknown>`. That's a type error, not
  a behaviour change.

- Font sizes now scale from a single `--zv-font-size` token. The sizes
  you see are unchanged.
- The members in `<OrganizationProfile>` sit inside a new `zv-member-list`
  element, and each row is a `zv-member`. If you styled these rows by
  their inline styles, use the new classes.
