GeezerVison is showing github.com — feat(expo): add native-to-web session transfer APIs by wobsoriano · Pull Request #10767 · clerk/javascript
Conversation
Description
A signed-in Expo app can now open a web page that is already signed in as the same user. The page can open in a WebView the app renders or in the system browser. The SDK asks Clerk for a one-time sign-in URL, which is valid for 60 seconds, right before it opens the page. FAPI's session_transfers endpoints are live behind a per-instance flag.
@clerk/expo/native-to-web has three exports.
useAuthenticatedWebView({ destination }) prepares the URL when the screen mounts. It returns webViewProps to spread onto a react-native-webview <WebView>, and a status of preparing, ready, signed-out, or error. status stays preparing while the WebView is on Clerk's "Signing you in" page, so the app can keep its own overlay up until the destination loads. The hook matches the redeem page by origin and path, so it also works for instances behind a proxy. If redeem fails, the hook reads __clerk_error_code from the landing URL and reports it as error.code. It accepts both the current __clerk_status=failed and session_transfer_failed, which clerk_go #23983 changes it to.
useNativeToWeb().openExternalBrowser({ destination }) opens the default browser. iOS uses Linking.openURL. Android Chrome refuses an app-opened URL unless the intent names the app as referrer. On Android the opener starts a VIEW intent through expo-intent-launcher, with the String extra android.intent.extra.REFERRER_NAME set to android-app://<package>. Chrome ignores a String EXTRA_REFERRER, but it turns REFERRER_NAME into Referer: android-app://<package>/. That means @clerk/expo needs no native code of its own.
isSessionTransferError(error). Every failure is an Error & { code }. FAPI refusal codes and redeem codes pass through unchanged, and the ClerkAPIResponseError stays on cause.
@clerk/expo/native-to-web/advanced exports prepareSessionTransfer({ destination, presentation }) for apps that present the URL themselves. The url property of the returned object is not enumerable, so console.log and JSON.stringify leave it out.
React Strict Mode and remounts don't create a second live transfer. A module-level ledger hands an in-flight or unused transfer to the next presenter that asks for the same session and destination. A transfer is marked used as soon as its URL reaches a WebView or the browser.
The first commit moves the FAPI request helpers from utils/hostedAuth.ts to utils/fapiRequest.ts, and getExpoAppIdentifier from useHostedAuth.ts to utils/appIdentifier.ts. Hosted auth now uses the shared copies. Its behavior doesn't change and its tests are unchanged.
expo-intent-launcher is a new optional peer. react-native-webview is a devDependency for a type test only. Its props are typed structurally, so it is not a peer.
I ran it with an Expo SDK 58 app against a development instance that has the flag on, on an iPhone 18 Pro simulator (iOS 27) and an Android API 36 emulator with Chrome. Each path below ended signed in:
| Path |
iOS |
Android |
useAuthenticatedWebView |
Signed in, ready |
Signed in, ready |
openExternalBrowser |
Safari, signed in |
Chrome via REFERRER_NAME, signed in |
| WebView mounted 65 s after mint |
error: expired, retry() signs in |
error: expired, retry() signs in |
There is one known gap. On iOS, onLoadEnd for the destination fires before WebKit paints the new page. When the overlay drops, the "Signing you in" page shows for one frame, about 33 ms, then the destination paints. Android didn't show this.
Checklist
-
pnpm test runs as expected.
-
pnpm build runs as expected.
- (If applicable) JSDoc comments have been added or updated for any package exports
- (If applicable) Documentation has been updated
Type of change
- 🐛 Bug fix
- 🌟 New feature
- 🔨 Breaking change
- 📖 Refactoring / dependency upgrade / documentation
- other:
🤖 Generated with Claude under Rob's supervision.
Part of MOBILE-450
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
vercel
Bot
commented
Nov 11, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
coderabbitai
Bot
commented
Nov 11, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|

Walkthrough
-
The change adds Expo APIs to transfer a signed-in session to a native WebView or external browser. It adds transfer validation, error handling, transfer reuse, and WebView navigation status handling. Android browser launches use a VIEW intent with an app referrer. Shared FAPI response helpers are also used by hosted auth. Package exports, dependencies, documentation, and declaration typechecking are updated.
Estimated code review effort: 4 (Complex) | ~60 minutes
Suggested reviewers: mikepitre, seanperez29
Merge Risk: Minimal · up to 09a44
-
This adds opt-in native-to-web session transfer APIs and moves shared request helpers behind unchanged hosted-auth behavior. No concrete merge-blocking risk was identified in the supplied evidence. Normal checks on the built package entry points are still advisable.
Pre-merge checks: 4 · 1
-
Failed checks: 1 warning
| Check name |
Status |
Explanation |
Resolution |
| Docstring Coverage |
 |
Docstring coverage is 8.11% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat… |
Write docstrings for the functions missing them to satisfy the coverage threshold. |
Passed checks: 4 passed
-
| Check name |
Status |
Explanation |
| Title check |
 |
The title clearly and concisely identifies the main change: new native-to-web session transfer APIs for Expo. |
| Description check |
 |
The description directly explains the new Expo session transfer APIs, supported WebView and browser flows, dependencies, testing, and known limitations. |
| Linked Issues check |
 |
Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check |
 |
Check skipped because no linked issues were found for this pull request. |
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 8.11% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 37 functions across 22 files. (5 skipped: 5 unsupported.)
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Warning Linked repositories: Some configured repositories could not be included in this review. Cross-repository analysis may be incomplete. Please check linked-repository access and settings.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Open in StackBlitz
@clerk/astro
npm i https://pkg.pr.new/@clerk/astro@10767
@clerk/backend
npm i https://pkg.pr.new/@clerk/backend@10767
@clerk/chrome-extension
npm i https://pkg.pr.new/@clerk/chrome-extension@10767
@clerk/clerk-js
npm i https://pkg.pr.new/@clerk/clerk-js@10767
@clerk/electron
npm i https://pkg.pr.new/@clerk/electron@10767
@clerk/electron-passkeys
npm i https://pkg.pr.new/@clerk/electron-passkeys@10767
@clerk/eslint-plugin
npm i https://pkg.pr.new/@clerk/eslint-plugin@10767
@clerk/expo
npm i https://pkg.pr.new/@clerk/expo@10767
@clerk/expo-biometrics
npm i https://pkg.pr.new/@clerk/expo-biometrics@10767
@clerk/expo-google-signin
npm i https://pkg.pr.new/@clerk/expo-google-signin@10767
@clerk/expo-native-components
npm i https://pkg.pr.new/@clerk/expo-native-components@10767
@clerk/expo-passkeys
npm i https://pkg.pr.new/@clerk/expo-passkeys@10767
@clerk/express
npm i https://pkg.pr.new/@clerk/express@10767
@clerk/fastify
npm i https://pkg.pr.new/@clerk/fastify@10767
@clerk/hono
npm i https://pkg.pr.new/@clerk/hono@10767
@clerk/localizations
npm i https://pkg.pr.new/@clerk/localizations@10767
@clerk/mosaic
npm i https://pkg.pr.new/@clerk/mosaic@10767
@clerk/nextjs
npm i https://pkg.pr.new/@clerk/nextjs@10767
@clerk/nuxt
npm i https://pkg.pr.new/@clerk/nuxt@10767
@clerk/react
npm i https://pkg.pr.new/@clerk/react@10767
@clerk/react-router
npm i https://pkg.pr.new/@clerk/react-router@10767
@clerk/shared
npm i https://pkg.pr.new/@clerk/shared@10767
@clerk/tanstack-react-start
npm i https://pkg.pr.new/@clerk/tanstack-react-start@10767
@clerk/testing
npm i https://pkg.pr.new/@clerk/testing@10767
@clerk/ui
npm i https://pkg.pr.new/@clerk/ui@10767
@clerk/upgrade
npm i https://pkg.pr.new/@clerk/upgrade@10767
@clerk/vue
npm i https://pkg.pr.new/@clerk/vue@10767
commit: 09a44a3
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
API Changes Report
Generated by Break Check on 2026-11-11T01:57:26.737Z
Summary
| Metric |
Count |
| Packages analyzed |
20 |
| Packages with changes |
1 |
| 🔴 Breaking changes |
0 |
| 🟡 Non-breaking changes |
0 |
| 🟢 Additions |
2 |
@clerk/expo
Current version: 4.15.1
Recommended bump: MINOR → 4.16.0
Subpath ./native-to-web
🟢 Additions (1)
Added: ./native-to-web
New subpath export ./native-to-web (10 exported members)
Subpath ./native-to-web/advanced
🟢 Additions (1)
Added: ./native-to-web/advanced
New subpath export ./native-to-web/advanced (4 exported members)
Report generated by Break Check
Last ran on 09a44a3.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
WebView hook signs in once, then ready 
Under Strict Mode one transfer was minted; the hook stayed preparing through the proxied redeem page and turned ready when the destination loaded signed in.

Also checked · 4 match · 1 differs · 1 not run
Failed redeem reports its code; retry recovers
Signed in on a real device · Blocked
FAPI refusal passes through unchanged
Transfer URL hidden from logs
Android browser intent carries REFERRER_NAME
Hosted auth unchanged by refactor
Warning Doesn't do what was asked
Failed redeem reports its code; retry recovers
Both status values gave error expired and retry() minted a second URL that ended ready and signed in; but when the failed page's onLoadEnd lands after retry's new props, the new attempt stays error expired even after the WebView signs in (2 of ~10 natural runs, reproduced determi...
Proof on 09a44a3d · mocked: Clerk Frontend API session_transfers (setting session transfers instance flag), react-native-webview (No iOS simulator here; a Playwright Chromium page loads the URL and forwards its...), expo-intent-launcher (No Android emulator; a recording stub captures startActivityAsync arguments) · not run: iOS simulator / Android emulator (Linux sandbox has no iOS Simulator, and no instance here has session transfers e...) · Did Proof get it right? Yes · No
View full Proof report · React 👎 or comment @codesmith dismiss this proof to dismiss
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This branch was successfully deployed
2 active
deployments
Reviewers
At least 1 approving review is required to merge this pull request.
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
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.
|