You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR brings Expo's native prebuilt components closer to the iOS and Android SDKs:
AuthView and UserProfileView are now renderable content. App code owns whether they appear in a modal, sheet, route, or full-screen root.
UserButton is backed by the platform-native Clerk button instead of a JS avatar/button implementation.
Native and JS auth state sync by refreshing both SDK clients toward the Clerk backend client, not by mirroring sign-out or interpreting component-level auth events.
Android native registration moved to Expo Modules. iOS stays on the existing app-target bridge until Expo has a clean SPM-compatible path for this setup.
Motivation: This matches the native SDK model: the prebuilt component owns its content and built-in dismissal affordance, while the app owns presentation.
UserButton Is The Native Button Surface
Before:UserButton was a JS-managed avatar/profile launcher and accepted JS layout props.
<UserButtonstyle={{width: 40,height: 40}}/>
After:UserButton wraps the platform-native Clerk button directly.
<UserButton/>
Motivation: The Expo API now mirrors the mobile SDKs: tapping the native button opens the native user profile, and Expo does not reimplement avatar/profile behavior in JS.
Native And JS Sync Through Client Refresh
Before: Native prebuilt views emitted auth-specific events such as sign-in/sign-out completion, and Expo JS interpreted those events or mirrored sign-out across SDKs.
After: Both sides refresh toward the Clerk backend client:
Native-to-JS: iOS observes Clerk iOS client changes; Android observes Clerk.clientFlow; both emit an internal refreshClient event.
JS-to-native: ClerkProvider observes Clerk JS resource changes, syncs the current JS client token to native when one exists, and otherwise asks the native SDK to refresh its current client.
Motivation: The backend client endpoint stays the source of truth. Expo no longer needs component-level auth semantics, duplicate sign-out calls, or one-off session syncing paths.
Android Uses Expo Modules
Before: Android used React Native package/view-manager registration plus presentation activities/factory paths for the prebuilt surfaces.
After: Android registers the Clerk module and native prebuilt views through Expo Modules. JS uses requireNativeModule and requireNativeView for Android.
Motivation: Expo Modules are the cleaner fit for Android now. iOS intentionally stays on the existing React Native bridge plus app-target native bridge because Clerk iOS is supplied to the consuming app through SPM, and the CocoaPods-backed Expo module target cannot cleanly import that app-target SPM dependency yet.
Generated by Break Check on 2026-06-09T20:14:25.422Z
Summary
Metric
Count
Packages analyzed
19
Packages with changes
1
🔴 Breaking changes
15
🟡 Non-breaking changes
3
🟢 Additions
4
Warning
15 breaking change(s) detected - Major version bump required
🤖 This report was reviewed by claude-sonnet-4-6.
Note
Break Check could not snapshot 3 subpaths; the diff below excludes them.
@clerk/astro ./env: Internal Error: Unable to determine module for: /home/runner/_work/javascript/javascript/packages/astro/env.d.ts You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.
@clerk/shared ./cookie: Internal Error: Unable to follow symbol for "Cookies" You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.
@clerk/testing ./cypress: Symbol not found for identifier: Cypress
🔴 Breaking changes index (15)
Every breaking change, up front. Full diffs are in the package sections below.
Package
Subpath
Change
@clerk/expo
.
NativeAuthStateEvent
@clerk/expo
.
NativeAuthStateEvent.sessionId
@clerk/expo
.
NativeAuthStateEvent.type
@clerk/expo
.
useNativeAuthEvents
@clerk/expo
.
UseNativeAuthEventsReturn
@clerk/expo
.
UseNativeAuthEventsReturn.isSupported
@clerk/expo
.
UseNativeAuthEventsReturn.nativeAuthState
@clerk/expo
.
useUserProfileModal
@clerk/expo
.
UseUserProfileModalReturn
@clerk/expo
.
UseUserProfileModalReturn.isAvailable
@clerk/expo
.
UseUserProfileModalReturn.presentUserProfile
@clerk/expo
./native
AuthViewProps.isDismissable
@clerk/expo
./native
UserButton
@clerk/expo
./native
UserButtonProps
@clerk/expo
./native
UserProfileViewProps.isDismissable
@clerk/expo
Current version: 3.3.1 Recommended bump: MAJOR → 4.0.0
🤖 AI review (confirmed) (97%): The NativeAuthStateEvent interface was removed from the public API, breaking any consumer code that references it by name.
Migration: Remove references to NativeAuthStateEvent or inline the equivalent type { sessionId: string | null; type: 'signedIn' | 'signedOut' } in your code.
🤖 AI review (confirmed) (97%): The sessionId property was removed as part of the removal of the NativeAuthStateEvent interface, breaking consumers that access it.
Migration: Update code that reads NativeAuthStateEvent.sessionId as the interface no longer exists.
🤖 AI review (confirmed) (97%): The type property was removed as part of the removal of the NativeAuthStateEvent interface, breaking consumers that access it.
Migration: Update code that reads NativeAuthStateEvent.type as the interface no longer exists.
Changed: useNativeAuthEvents
- export declare function useNativeAuthEvents(): UseNativeAuthEventsReturn;
Static analyzer: Removed function useNativeAuthEvents
🤖 AI review (confirmed) (97%): The exported function useNativeAuthEvents was removed, breaking any consumer code that calls it.
Migration: Remove calls to useNativeAuthEvents and implement equivalent native auth event handling directly.
🤖 AI review (confirmed) (97%): The UseNativeAuthEventsReturn interface was removed from the public API, breaking consumers that reference it as a type.
Migration: Remove references to UseNativeAuthEventsReturn or define an equivalent local type in your code.
🤖 AI review (confirmed) (97%): The isSupported property was removed as part of the removal of UseNativeAuthEventsReturn, breaking consumers that access it.
Migration: Update code that reads UseNativeAuthEventsReturn.isSupported as the interface no longer exists.
🤖 AI review (confirmed) (97%): The nativeAuthState property was removed as part of the removal of UseNativeAuthEventsReturn, breaking consumers that access it.
Migration: Update code that reads UseNativeAuthEventsReturn.nativeAuthState as the interface no longer exists.
Changed: useUserProfileModal
- export declare function useUserProfileModal(): UseUserProfileModalReturn;
Static analyzer: Removed function useUserProfileModal
🤖 AI review (confirmed) (97%): The exported function useUserProfileModal was removed, breaking any consumer code that calls it.
Migration: Remove calls to useUserProfileModal and implement equivalent user profile modal logic directly.
🤖 AI review (confirmed) (97%): The UseUserProfileModalReturn interface was removed from the public API, breaking consumers that reference it as a type.
Migration: Remove references to UseUserProfileModalReturn or define an equivalent local type in your code.
🤖 AI review (confirmed) (97%): The isAvailable property was removed as part of the removal of UseUserProfileModalReturn, breaking consumers that access it.
Migration: Update code that reads UseUserProfileModalReturn.isAvailable as the interface no longer exists.
🤖 AI review (confirmed) (97%): The presentUserProfile property was removed as part of the removal of UseUserProfileModalReturn, breaking consumers that call it.
Migration: Update code that calls UseUserProfileModalReturn.presentUserProfile as the interface no longer exists.
Static analyzer: Breaking change in property TokenCache.clearToken: Type changed: clearToken?:(key:string)=>void; → clearToken?:(key:string)=>void|!Promise:interface<void>;
🤖 AI review (reclassified as non-breaking) (92%): The return type of clearToken was widened from void to void | Promise<void>; callers that previously ignored the return value remain valid, and this is an optional property so any existing implementation returning void still satisfies the new union type.
🤖 AI review (confirmed) (95%): The optional property isDismissable was removed from AuthViewProps; any consumer passing isDismissable in an object literal will get a type error.
Migration: Replace isDismissable with the new isDismissible property (and onDismiss for the callback) when constructing AuthViewProps objects.
Changed: UserButton
- export declare function UserButton(_props: UserButtonProps): import("react/jsx-runtime").JSX.Element;+ export declare function UserButton(): import("react/jsx-runtime").JSX.Element | null;
Static analyzer: Breaking change in function UserButton: Return type changed: import("@types/react").JSX.Element → import("@types/react").JSX.Element|null; Parameter _props was removed
🤖 AI review (confirmed) (95%): The _props parameter was removed from UserButton, so any consumer calling UserButton(someProps) will no longer compile.
Migration: Remove any arguments passed to UserButton; it now takes no parameters.
🤖 AI review (confirmed) (95%): UserButtonProps was a publicly exported interface; removing it breaks any consumer that imported and used the type explicitly.
Migration: Remove all imports and usages of UserButtonProps from consumer code, as UserButton no longer accepts props.
🤖 AI review (confirmed) (90%): The optional property isDismissable was removed from UserProfileViewProps; consumers passing it will get a type error.
Migration: Replace isDismissable with isDismissible (following the same rename pattern as AuthViewProps) when constructing UserProfileViewProps objects.
🟡 Non-breaking Changes (1)
Modified: AuthView
- export declare function AuthView(input: AuthViewProps): import("react/jsx-runtime").JSX.Element;+ export declare function AuthView(input: AuthViewProps): ReactElement;
Static analyzer: Breaking change in function AuthView: Return type changed: import("@types/react").JSX.Element → import("@types/react").React.ReactElement
🤖 AI review (reclassified as non-breaking) (90%): JSX.Element and React.ReactElement are structurally equivalent (JSX.Element is an alias for React.ReactElement), so callers consuming the return value are unaffected.
Static analyzer: Breaking change in property TokenCache.clearToken: Type changed: clearToken?:(key:string)=>void; → clearToken?:(key:string)=>void|!Promise:interface<void>;
🤖 AI review (reclassified as non-breaking) (95%): The return type of clearToken was widened from void to void | Promise<void>. Existing consumers who implemented clearToken returning void still satisfy the new type, and callers who ignored the return value are unaffected; this is a safe output-type widening (contravariant input position: implementors only need to satisfy the broader union).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR brings Expo's native prebuilt components closer to the iOS and Android SDKs:
AuthViewandUserProfileVieware now renderable content. App code owns whether they appear in a modal, sheet, route, or full-screen root.UserButtonis backed by the platform-native Clerk button instead of a JS avatar/button implementation.Updated Docs PR clerk/clerk-docs#3424
What Changed
Prebuilt Views Are App-Presented Content
Before: Expo owned presentation through imperative helpers and Android activities, and profile presentation used
useUserProfileModal().After:
AuthViewandUserProfileViewrender as content. Apps present them in their own modal, sheet, route, or root view.Motivation: This matches the native SDK model: the prebuilt component owns its content and built-in dismissal affordance, while the app owns presentation.
UserButton Is The Native Button Surface
Before:
UserButtonwas a JS-managed avatar/profile launcher and accepted JS layout props.After:
UserButtonwraps the platform-native Clerk button directly.Motivation: The Expo API now mirrors the mobile SDKs: tapping the native button opens the native user profile, and Expo does not reimplement avatar/profile behavior in JS.
Native And JS Sync Through Client Refresh
Before: Native prebuilt views emitted auth-specific events such as sign-in/sign-out completion, and Expo JS interpreted those events or mirrored sign-out across SDKs.
After: Both sides refresh toward the Clerk backend client:
Clerk.clientFlow; both emit an internalrefreshClientevent.ClerkProviderobserves Clerk JS resource changes, syncs the current JS client token to native when one exists, and otherwise asks the native SDK to refresh its current client.Motivation: The backend client endpoint stays the source of truth. Expo no longer needs component-level auth semantics, duplicate sign-out calls, or one-off session syncing paths.
Android Uses Expo Modules
Before: Android used React Native package/view-manager registration plus presentation activities/factory paths for the prebuilt surfaces.
After: Android registers the Clerk module and native prebuilt views through Expo Modules. JS uses
requireNativeModuleandrequireNativeViewfor Android.Motivation: Expo Modules are the cleaner fit for Android now. iOS intentionally stays on the existing React Native bridge plus app-target native bridge because Clerk iOS is supplied to the consuming app through SPM, and the CocoaPods-backed Expo module target cannot cleanly import that app-target SPM dependency yet.