diff --git a/.changeset/expo-native-build-smoke.md b/.changeset/expo-native-build-smoke.md
new file mode 100644
index 00000000000..a845151cc84
--- /dev/null
+++ b/.changeset/expo-native-build-smoke.md
@@ -0,0 +1,2 @@
+---
+---
diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml
new file mode 100644
index 00000000000..2018095c880
--- /dev/null
+++ b/.github/workflows/expo-native-build.yml
@@ -0,0 +1,114 @@
+name: 'Expo native build (@clerk/expo)'
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ branches:
+ - main
+ paths:
+ - '.github/workflows/expo-native-build.yml'
+ - 'packages/expo/**'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: expo-native-build-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
+env:
+ FIXTURE_DIR: packages/expo/e2e/native-build-fixture
+ FIXTURE_PUBLISHABLE_KEY: pk_test_ZHVtbXkuY2xlcmsuYWNjb3VudHMuZGV2JA
+ SDK_PACK_DIR: /tmp/clerk-expo-pack
+
+jobs:
+ native-build:
+ name: Expo ${{ matrix.expo-sdk }} / ${{ matrix.platform }}
+ runs-on: ${{ matrix.runner }}
+ timeout-minutes: 45
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - expo-sdk: 54
+ platform: android
+ runner: blacksmith-8vcpu-ubuntu-2204
+ - expo-sdk: 54
+ platform: ios
+ runner: blacksmith-6vcpu-macos-26
+ - expo-sdk: 56
+ platform: android
+ runner: blacksmith-8vcpu-ubuntu-2204
+ - expo-sdk: 56
+ platform: ios
+ runner: blacksmith-6vcpu-macos-26
+
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ with:
+ persist-credentials: false
+
+ - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: 24.15.0
+ cache: pnpm
+
+ - name: Install monorepo dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build and pack @clerk/expo
+ run: |
+ pnpm --filter @clerk/expo... build
+ mkdir -p "$SDK_PACK_DIR"
+ pnpm --filter @clerk/expo pack --pack-destination "$SDK_PACK_DIR"
+ SDK_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-*.tgz)"
+ if tar -tf "$SDK_TARBALL" | grep -q '^package/e2e/'; then
+ echo "Fixture files must not be packed into @clerk/expo"
+ exit 1
+ fi
+
+ - name: Install fixture dependencies
+ working-directory: ${{ env.FIXTURE_DIR }}
+ env:
+ EXPO_SDK: ${{ matrix.expo-sdk }}
+ run: |
+ cp "package.sdk-$EXPO_SDK.json" package.json
+ pnpm install --no-frozen-lockfile
+ SDK_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-*.tgz)"
+ pnpm add "$SDK_TARBALL" -w
+ pnpm expo install expo-auth-session expo-constants expo-crypto expo-dev-client expo-secure-store expo-web-browser
+
+ - name: Write fixture .env
+ working-directory: ${{ env.FIXTURE_DIR }}
+ run: |
+ echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=$FIXTURE_PUBLISHABLE_KEY" > .env
+
+ - name: Set up JDK 17
+ if: ${{ matrix.platform == 'android' }}
+ uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
+ with:
+ distribution: temurin
+ java-version: 17
+
+ - name: Prebuild Android fixture
+ if: ${{ matrix.platform == 'android' }}
+ working-directory: ${{ env.FIXTURE_DIR }}
+ run: pnpm prebuild:android
+
+ - name: Build Android fixture
+ if: ${{ matrix.platform == 'android' }}
+ working-directory: ${{ env.FIXTURE_DIR }}
+ run: pnpm build:android
+
+ - name: Prebuild iOS fixture
+ if: ${{ matrix.platform == 'ios' }}
+ working-directory: ${{ env.FIXTURE_DIR }}
+ run: pnpm prebuild:ios
+
+ - name: Build iOS fixture
+ if: ${{ matrix.platform == 'ios' }}
+ working-directory: ${{ env.FIXTURE_DIR }}
+ run: pnpm build:ios
diff --git a/packages/expo/e2e/native-build-fixture/.gitignore b/packages/expo/e2e/native-build-fixture/.gitignore
new file mode 100644
index 00000000000..ec43c5d5c09
--- /dev/null
+++ b/packages/expo/e2e/native-build-fixture/.gitignore
@@ -0,0 +1,7 @@
+/.expo/
+/.env
+/android/
+/ios/
+/node_modules/
+/package.json
+/pnpm-lock.yaml
diff --git a/packages/expo/e2e/native-build-fixture/App.tsx b/packages/expo/e2e/native-build-fixture/App.tsx
new file mode 100644
index 00000000000..4d549077e3e
--- /dev/null
+++ b/packages/expo/e2e/native-build-fixture/App.tsx
@@ -0,0 +1,73 @@
+import { ClerkProvider, useAuth, useUser } from '@clerk/expo';
+import { AuthView, UserButton } from '@clerk/expo/native';
+import { tokenCache } from '@clerk/expo/token-cache';
+import { useState } from 'react';
+import { Button, Modal, StyleSheet, Text, View } from 'react-native';
+
+const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
+
+if (!publishableKey) {
+ throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY');
+}
+
+function NativeBuildFixture() {
+ const { isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false });
+ const { user } = useUser();
+ const [isAuthOpen, setIsAuthOpen] = useState(false);
+
+ return (
+
+
+ Clerk Expo Native Fixture
+ {isSignedIn && }
+
+
+ {isLoaded ? `signed ${isSignedIn ? 'in' : 'out'}` : 'loading'}
+ {user?.id && {user.id}}
+
+ );
+}
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ gap: 16,
+ justifyContent: 'center',
+ padding: 24,
+ },
+ header: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ gap: 12,
+ justifyContent: 'space-between',
+ },
+ title: {
+ flex: 1,
+ fontSize: 20,
+ fontWeight: '600',
+ },
+});
diff --git a/packages/expo/e2e/native-build-fixture/app.json b/packages/expo/e2e/native-build-fixture/app.json
new file mode 100644
index 00000000000..58e258ea523
--- /dev/null
+++ b/packages/expo/e2e/native-build-fixture/app.json
@@ -0,0 +1,17 @@
+{
+ "expo": {
+ "name": "Clerk Expo Native Build Fixture",
+ "slug": "clerk-expo-native-build-fixture",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "scheme": "clerkexponativebuildfixture",
+ "ios": {
+ "bundleIdentifier": "com.clerk.exponativebuildfixture",
+ "supportsTablet": true
+ },
+ "android": {
+ "package": "com.clerk.exponativebuildfixture"
+ },
+ "plugins": ["expo-secure-store", "@clerk/expo", "expo-web-browser"]
+ }
+}
diff --git a/packages/expo/e2e/native-build-fixture/index.js b/packages/expo/e2e/native-build-fixture/index.js
new file mode 100644
index 00000000000..ce8f2073fba
--- /dev/null
+++ b/packages/expo/e2e/native-build-fixture/index.js
@@ -0,0 +1,5 @@
+import { registerRootComponent } from 'expo';
+
+import App from './App';
+
+registerRootComponent(App);
diff --git a/packages/expo/e2e/native-build-fixture/package.sdk-54.json b/packages/expo/e2e/native-build-fixture/package.sdk-54.json
new file mode 100644
index 00000000000..445437dd99e
--- /dev/null
+++ b/packages/expo/e2e/native-build-fixture/package.sdk-54.json
@@ -0,0 +1,23 @@
+{
+ "name": "clerk-expo-native-build-fixture",
+ "version": "1.0.0",
+ "private": true,
+ "main": "index.js",
+ "scripts": {
+ "build:android": "cd android && ./gradlew assembleRelease",
+ "build:ios": "xcodebuild -workspace ios/ClerkExpoNativeBuildFixture.xcworkspace -scheme ClerkExpoNativeBuildFixture -configuration Release -sdk iphonesimulator -derivedDataPath ios/build CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=''",
+ "prebuild:android": "expo prebuild --clean --platform android",
+ "prebuild:ios": "expo prebuild --clean --platform ios"
+ },
+ "dependencies": {
+ "expo": "~54.0.34",
+ "react": "19.1.0",
+ "react-native": "0.81.5"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.29.0",
+ "@types/react": "~19.1.17",
+ "typescript": "~5.9.3"
+ },
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
+}
diff --git a/packages/expo/e2e/native-build-fixture/package.sdk-56.json b/packages/expo/e2e/native-build-fixture/package.sdk-56.json
new file mode 100644
index 00000000000..2f7db0a02bd
--- /dev/null
+++ b/packages/expo/e2e/native-build-fixture/package.sdk-56.json
@@ -0,0 +1,23 @@
+{
+ "name": "clerk-expo-native-build-fixture",
+ "version": "1.0.0",
+ "private": true,
+ "main": "index.js",
+ "scripts": {
+ "build:android": "cd android && ./gradlew assembleRelease",
+ "build:ios": "xcodebuild -workspace ios/ClerkExpoNativeBuildFixture.xcworkspace -scheme ClerkExpoNativeBuildFixture -configuration Release -sdk iphonesimulator -derivedDataPath ios/build CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=''",
+ "prebuild:android": "expo prebuild --clean --platform android",
+ "prebuild:ios": "expo prebuild --clean --platform ios"
+ },
+ "dependencies": {
+ "expo": "~56.0.12",
+ "react": "19.2.3",
+ "react-native": "0.85.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.29.0",
+ "@types/react": "~19.1.17",
+ "typescript": "~5.9.3"
+ },
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
+}
diff --git a/packages/expo/e2e/native-build-fixture/pnpm-workspace.yaml b/packages/expo/e2e/native-build-fixture/pnpm-workspace.yaml
new file mode 100644
index 00000000000..4de91a383ab
--- /dev/null
+++ b/packages/expo/e2e/native-build-fixture/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+packages:
+ - '.'
diff --git a/packages/expo/e2e/native-build-fixture/tsconfig.json b/packages/expo/e2e/native-build-fixture/tsconfig.json
new file mode 100644
index 00000000000..b9567f6052d
--- /dev/null
+++ b/packages/expo/e2e/native-build-fixture/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "extends": "expo/tsconfig.base",
+ "compilerOptions": {
+ "strict": true
+ }
+}