c38d853, 7e3174a, 97039bb, f43071d, 0e0ff11, 0039618, a536a0d]:
01789b4]:
preferredSignInStrategyWhenPasswordRequired parameter to clerkClient.instance.update(). Accepts 'password' or 'otp' to override the preferred sign-in strategy when a password is required, or an empty string to clear the override. (#8878) by @dmoernerf4167ec, 17e4164, ed2cf75, 67c04a4, 51c8fdc, c2ba971, 8744728, d9b5c7d]:
Add clerkClient.organizations.replaceOrganizationMetadata(organizationId, params) for replacing an organization's metadata fields in full. (#8787) by @brunol95
Use replaceOrganizationMetadata when the provided metadata should become the complete value for that metadata field:
await clerkClient.organizations.replaceOrganizationMetadata(organizationId, {
publicMetadata: { plan: 'pro' },
});
Use clerkClient.organizations.updateOrganizationMetadata(organizationId, params) when you want to partially update metadata with deep-merge semantics:
await clerkClient.organizations.updateOrganizationMetadata(organizationId, {
publicMetadata: { onboardingComplete: true },
});
The publicMetadata and privateMetadata parameters on clerkClient.organizations.updateOrganization() are now deprecated. They continue to work, but new code should use updateOrganizationMetadata() for partial updates or replaceOrganizationMetadata() for full replacement.
Add clerkClient.users.replaceUserMetadata(userId, params) for replacing a user's metadata fields in full. (#8587) by @brunol95
Use replaceUserMetadata when the provided metadata should become the complete value for that metadata field:
await clerkClient.users.replaceUserMetadata(userId, {
publicMetadata: { plan: 'pro' },
});
Use clerkClient.users.updateUserMetadata(userId, params) when you want to partially update metadata with deep-merge semantics:
await clerkClient.users.updateUserMetadata(userId, {
publicMetadata: { onboardingComplete: true },
});
The publicMetadata, privateMetadata, and unsafeMetadata parameters on clerkClient.users.updateUser() are now deprecated. They continue to work, but new code should use updateUserMetadata() for partial updates or replaceUserMetadata() for full replacement.
Migrate the build pipeline to tsdown and TypeScript 6.0. This is an internal tooling change with no intended changes to the public API or runtime behavior. (#8177) by @dstaley
Updated dependencies [f046c49, b5fa9f6, 3d5b2fe]:
a5c7bc7]:
createClerkClient() now exposes: (#8774) by @dmoerner
organizationPermissions — list, get, create, update, and delete organization permissions.organizationRoles — list, get, create, update, and delete organization roles, plus assign/remove a permission to/from a role.roleSets — list, get, create, update, add roles to, replace a role in, and replace a role set.Fix the return type of clerkClient.organizations.createOrganizationInvitationBulk() to PaginatedResourceResponse<OrganizationInvitation[]>. The Backend API returns the bulk-created invitations in a { data, totalCount } envelope (the same shape as getOrganizationInvitationList()), but the method was typed as OrganizationInvitation[], which did not match the value returned at runtime. (#8751) by @VihAMBR
Return IdPOAuthAccessToken timestamps in milliseconds when an OAuth access token is verified as a JWT. The expiration, createdAt, and updatedAt fields were previously populated with the JWT's raw second-based exp/iat values, making them inconsistent with the same fields on M2MToken and with the values returned when the token is fetched from the API. Comparing expiration against Date.now() now behaves as expected. The expired flag was already computed correctly and is unaffected. (#8771) by @jacekradko
Prevent an unhandled exception when verifying a machine token whose JWT payload has a missing or non-string sub. Such tokens are now classified and rejected with a typed verification error instead of throwing, so a crafted Authorization header can no longer surface as an unhandled error during request authentication. (#8744) by @jacekradko
Redact raw bearer credentials from the auth object's debug output. The debug payload (surfaced when an SDK enables middleware debug logging) previously included full session, machine, refresh, dev-browser and handshake tokens; each now exposes only a short, non-reconstructable prefix, matching how secretKey and jwtKey are already handled. (#8744) by @jacekradko
Add and improve JSDoc comments across public types and methods to support generated reference documentation for the /objects docs section. Exports a few previously-internal types (OnEventListener, OffEventListener, ClerkOptionsNavigation) so they can be referenced from the generated docs. (#8276) by @alexisintech
Updated dependencies [2d6670c, af706e3, 032632c, 0fece6f, b295af3, 8e1bd48]:
users.replaceUserEmailAddress(userId, { emailAddress }) replaces all of a user's email addresses with a single verified, primary email address (PUT /users/{user_id}/email_address).users.replaceUserPhoneNumber(userId, { phoneNumber }) replaces all of a user's phone numbers with a single verified, primary phone number (PUT /users/{user_id}/phone_number).users.createUser now accepts banned and locked parameters to create a user that is already banned or locked.Emit the "session token from cookie is missing the azp claim" warning once per process instead of on every authenticated request. An azp-less cookie token is reused across requests, so the previous unguarded console.warn could flood production logs. (#8698) by @jacekradko
Stop authenticateRequest from consuming the incoming request body, which previously left downstream handlers unable to read it (for example a Hono POST route calling c.req.json()). (#8708) by @jacekradko
Prevent keyless mode from activating in CI and other automated environments in framework SDKs. (#8676) by @mwickett
Preserve custom claims when verifying JWT-format M2M tokens. M2MToken.fromJwtPayload previously hardcoded claims to null, so client.m2m.verify() (and request-level auth()) dropped any custom claims embedded in the token. Custom claims are now reconstructed from the verified payload by stripping only the structural claims the backend adds when minting the token (iss, sub, exp, nbf, iat, jti). User-supplied claims such as aud are preserved. Tokens without custom claims still return claims: null, consistent with the opaque-token path. (#8697) by @jacekradko
Strip private_metadata from the backend resource _raw payload in stripPrivateDataFromObject, preventing it from leaking into __clerk_ssr_state when a User/Organization resource is passed to buildClerkProps. (#8702) by @dominic-clerk
Updated dependencies [afb75e6, c3df67a, 86fd38f, 8d6bb56, 43dfefa, 5fc7b21, c2ba134]:
a036ce8]:
Adds agentTaskId and deprecates taskId to Agent Tasks Create response. (#8013) by @tmilewski
Updated dependencies [95f6c2f]:
4fc38a0]:
Support min_remaining_ttl_seconds for M2M token creation. (#8513) by @wobsoriano
Usage:
clerkClient.m2m.createToken({
machineSecretKey: 'ak_xxxxx',
minRemainingTtlSeconds: 240,
});
Add RoleSetJSON, RoleSetItemJSON, and RoleSetMigrationJSON types matching the BAPI OpenAPI schema. Add role_set_key, last_active_at, and missing_member_with_elevated_permissions to OrganizationJSON. (#8502) by @jacekradko
Updated dependencies [5cda3ee]:
Fix OAuth consent component and hook related types. (#8483) by @SarahSoutoul
Updated dependencies [7a5892f]:
1bfd8ab]:
Auto-proxy FAPI requests for .vercel.app subdomains. When deployed to a .vercel.app domain without explicit proxy or domain configuration, the SDK automatically routes Frontend API requests through /__clerk on the app's own origin. This enables Clerk production mode on Vercel deployments without manual proxy setup. (#8035) by @brkalow
Fix Request cloning and outbound fetch to omit cross-realm AbortSignal. Node 24's bundled undici tightened the instanceof AbortSignal check on RequestInit.signal, which broke: (#8351) by @jacekradko
NextRequest in @clerk/backend's ClerkRequest.Requests passed through patchRequest in @clerk/react-router and @clerk/tanstack-react-start.@clerk/backend's clerkFrontendApiProxy, which forwarded the inbound request's signal to the upstream fetch. Abort propagation will be restored in a follow-up via an in-realm AbortController bridge.Updated dependencies [9b57986, a9f9b29]:
da76490]:
createBootstrapSignedOutState helper to @clerk/backend/internal. Returns a synthetic UnauthenticatedState<'session_token'> without requiring a publishable key or an AuthenticateContext. Intended for framework integrations that need to run authorization logic before real Clerk keys are available (e.g. the Next.js keyless bootstrap window). Accepts optional signInUrl, signUpUrl, isSatellite, domain, and proxyUrl so that createRedirect-driven flows (including cross-origin satellite sign-in with the __clerk_status=needs-sync handshake marker) behave correctly during bootstrap. (#8368) by @jacekradkoA clock skew of 0 will not fall back to the default value anymore. (#8359) by @dominic-clerk
Updated dependencies [d52b311]:
joinPaths (#8331) by @dominic-clerkIntroduce samlConnection and oauthConfig into the EnterpriseConnection resource. (#8326) by @LauraBeatris
The JWT claims are verified after the signature to avoid leaking information through error messages on forged tokens. (#8332) by @dominic-clerk
Updated dependencies [c7b0f47, 34762e8]:
b0b6675]:
dc2de16]:
sec-fetch-dest: document incorrectly triggering handshake redirects, resulting in 405 errors from FAPI. Non-GET requests (e.g. native form submissions) are now excluded from handshake and multi-domain sync eligibility. (#8045) by @jacekradkoExport OrganizationInvitationAcceptedWebhookEvent type. (#8235) by @wobsoriano
Updated dependencies [2c06a5f]:
Export ClerkAPIResponseError and ClerkRuntimeError classes from error subpaths for consistency with the already-exported type guards. (#8228) by @jacekradko
feat: add orderBy argument to getInvitationList to control sorting (supports leading '+' for ascending and '-' for descending) (#7137) by @mario-jerkovic
Updated dependencies [b289566, 636b496, aa63796]:
Fix frontend API proxy following redirects server-side instead of passing them to the browser. The proxy's fetch() call now uses redirect: 'manual' so that 3xx responses from FAPI (e.g. after OAuth callbacks) are returned to the client as-is, matching standard HTTP proxy behavior. (#8186) by @brkalow
Improve the built-in Clerk Frontend API proxy, adding support for abort signals and addressing a number of small edge cases. (#8163) by @brkalow
Add EnterpriseAccount and EnterpriseAccountConnection classes to @clerk/backend, restoring enterprise SSO account data on the User object that was lost when samlAccounts was removed in v3. (#8181) by @iagodahlem
Updated dependencies [9a00a1c, 00715a6, b8c73d3, 1827b50, 7707a31]:
Fix ERR_CONTENT_DECODING_FAILED when loading proxied assets by requesting uncompressed responses from FAPI and stripping Content-Encoding/Content-Length headers that fetch() invalidates through auto-decompression. (#8159) by @brkalow
Fix satelliteAutoSync to default to false as documented. Previously, not passing the prop resulted in undefined, which was treated as true due to a strict equality check (=== false). This preserved Core 2 auto-sync behavior instead of the intended Core 3 default. The check is now !== true, so both undefined and false skip automatic satellite sync. (#8001) by @nikosdouvlis
Fix an issue where multiple set-cookie headers were being dropped by the frontend API proxy. (#8162) by @brkalow
b9cb6e5]:
EnterpriseConnection resource, allowing to create both OIDC and SAML connections (#8017) by @LauraBeatrisFix casing of enterprise connection API params when sending saml or oidc configuration (#8022) by @LauraBeatris
Updated dependencies [de1386f]:
3e63793]:
Fix clerkFrontendApiProxy to derive the Clerk-Proxy-Url header and Location rewrites from x-forwarded-proto/x-forwarded-host headers instead of the raw request.url. Behind a reverse proxy, request.url resolves to localhost, causing FAPI to receive an incorrect proxy URL. The fix uses the same forwarded-header resolution pattern as ClerkRequest. (#7994) by @nikosdouvlis
Remove experimental comment from Agent Tasks API (#7978) by @tmilewski
Updated dependencies [776ee1b, 7fb870d, 09cb6d4]:
package.json engine and peer dependency constraints. (#7972) by @jacekradkoRemove deprecated verify methods in favor of verify(). (#7927) by @wobsoriano
apiKeys.verifySecret() removed
// Before
await clerkClient.apiKeys.verifySecret(secret);
// After
await clerkClient.apiKeys.verify(secret);
idpOAuthAccessToken.verifyAccessToken() removed
// Before
await clerkClient.idpOAuthAccessToken.verifyAccessToken(accessToken);
// After
await clerkClient.idpOAuthAccessToken.verify(accessToken);
m2m.verifyToken() removed
// Before
await clerkClient.m2m.verifyToken(params);
// After
await clerkClient.m2m.verify(params);
Update engines config to require node@20 or higher (#6931) by @jacekradko
Remove deprecated samlAccount in favor of enterpriseAccount (#7258) by @LauraBeatris
Add support for Agent Tasks API endpoint which allows developers to create agent tasks that can be used to act on behalf of users through automated flows. (#7783) by @tmilewski
Add Frontend API proxy support via frontendApiProxy option in clerkMiddleware (#7602) by @brkalow
Add satelliteAutoSync option to optimize satellite app handshake behavior (#7597) by @nikosdouvlis
Satellite apps currently trigger a handshake redirect on every first page load, even when no cookies exist. This creates unnecessary redirects to the primary domain for apps where most users aren't authenticated.
New option: satelliteAutoSync (default: false)
false (default): Skip automatic handshake if no session cookies exist, only trigger after explicit sign-in actiontrue: Satellite apps automatically trigger handshake on first load (previous behavior)New query parameter: __clerk_sync
__clerk_sync=1 (NeedsSync): Triggers handshake after returning from primary sign-in__clerk_sync=2 (Completed): Prevents re-sync loop after handshake completesBackwards compatible: Still reads legacy __clerk_synced=true parameter.
SSR redirect fix: Server-side redirects (e.g., redirectToSignIn() from middleware) now correctly add __clerk_sync=1 to the return URL for satellite apps. This ensures the handshake is triggered when the user returns from sign-in on the primary domain.
CSR redirect fix: Client-side redirects now add __clerk_sync=1 to all redirect URL variants (forceRedirectUrl, fallbackRedirectUrl) for satellite apps, not just the default redirectUrl.
import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware({
isSatellite: true,
domain: 'satellite.example.com',
signInUrl: 'https://primary.example.com/sign-in',
// Set to true to automatically sync auth state on first load
satelliteAutoSync: true,
});
import { clerkMiddleware } from '@clerk/tanstack-react-start/server';
export default clerkMiddleware({
isSatellite: true,
domain: 'satellite.example.com',
signInUrl: 'https://primary.example.com/sign-in',
// Set to true to automatically sync auth state on first load
satelliteAutoSync: true,
});
<ClerkProvider
publishableKey='pk_...'
isSatellite={true}
domain='satellite.example.com'
signInUrl='https://primary.example.com/sign-in'
// Set to true to automatically sync auth state on first load
satelliteAutoSync={true}
>
{children}
</ClerkProvider>
import { clerkMiddleware } from '@clerk/tanstack-react-start/server';
// Options callback - receives context object, returns options
export default clerkMiddleware(({ url }) => ({
isSatellite: true,
domain: 'satellite.example.com',
signInUrl: 'https://primary.example.com/sign-in',
satelliteAutoSync: url.pathname.startsWith('/dashboard'),
}));
satelliteAutoSync defaults to falsePreviously, satellite apps would automatically trigger a handshake redirect on every first page load to sync authentication state with the primary domain—even when no session cookies existed. This caused unnecessary redirects to the primary domain for users who weren't authenticated.
The new default (satelliteAutoSync: false) provides a better experience for end users. Performance-wise, the satellite app can be shown immediately without attempting to sync state first, which is the right behavior for most use cases.
To preserve the previous behavior where visiting a satellite while already signed in on the primary domain automatically syncs your session, set satelliteAutoSync: true:
export default clerkMiddleware({
isSatellite: true,
domain: 'satellite.example.com',
signInUrl: 'https://primary.example.com/sign-in',
satelliteAutoSync: true, // Opt-in to automatic sync on first load
});
The clerkMiddleware function no longer accepts individual props as functions. If you were using the function form for props like domain, proxyUrl, or isSatellite, migrate to the options callback pattern.
Before (prop function form - no longer supported):
import { clerkMiddleware } from '@clerk/tanstack-react-start/server';
export default clerkMiddleware({
isSatellite: true,
// ❌ Function form for individual props no longer works
domain: url => url.hostname,
});
After (options callback form):
import { clerkMiddleware } from '@clerk/tanstack-react-start/server';
// ✅ Wrap entire options in a callback function
export default clerkMiddleware(({ url }) => ({
isSatellite: true,
domain: url.hostname,
}));
The callback receives a context object with the url property (a URL instance) and can return options synchronously or as a Promise for async configuration.