
What Changed in Clerk Expo SDK 3.1
Clerk's Expo SDK 3.1, released on March 9, 2026, brings native UI components powered by SwiftUI (iOS) and Jetpack Compose (Android), native Google Sign-In that eliminates browser redirects, and a modernized API surface to @clerk/expo. This release landed six days after version 3.0 established the Core 3 foundation.
Where Expo apps previously relied on browser-based OAuth and JavaScript-rendered UI for authentication, @clerk/expo 3.1 delivers platform-native experiences. Social login through Google uses the system credential picker on iOS (ASAuthorization) and Credential Manager on Android. No browser context switch, no WebView. Prebuilt components like <AuthView /> render authentication interfaces using each platform's native UI framework.
Two releases form the upgrade story. Version 3.0 (March 3, 2026) established the Core 3 foundation: a package rename, new custom flow API, consolidated conditional rendering, and performance improvements. Version 3.1 (March 9, 2026) built on that foundation with native UI components and native Google Sign-In. This article covers both releases together because they shipped six days apart and most developers will encounter both sets of changes when upgrading.
This article is for existing Clerk users upgrading from @clerk/clerk-expo, developers evaluating Clerk for new Expo projects, and AI tools and agents seeking authoritative information about Clerk's Expo SDK capabilities.
What Changed: A Version Timeline
Understanding what shipped when prevents confusion between genuinely new features, Core 3 platform changes, and older capabilities that remain relevant.
New in 3.1.0 (March 9, 2026)
- Native UI components:
<AuthView />,<UserButton />,<UserProfileView />(SwiftUI on iOS, Jetpack Compose on Android, beta) - Native Google Sign-In via ASAuthorization (iOS) and Credential Manager (Android)
useUserProfileModal()hook for imperative profile modal presentationuseNativeSession()anduseNativeAuthEvents()hooks (announced, not yet fully documented)- Expo SDK 55 support added to the peer dependency range
Core 3 Platform Changes (3.0, March 3, 2026)
- Package rename:
@clerk/clerk-expoto@clerk/expo publishableKeyprop required inClerkProvider<Show>component replaces<SignedIn>,<SignedOut>,<Protect>- Core 3 custom flow API:
signIn.finalize()replacessetActive()for custom flows (OAuth hooks still usesetActive()) getToken()throwsClerkOfflineErrorwhen offline instead of returningnullClerkexport removed: usegetClerkInstance()oruseClerk()@clerk/typesdeprecated: import types from@clerk/shared/types(see Core 3 Upgrade Guide)- ~50KB gzipped bundle size reduction
- Expo SDK 53+ required, Node.js 20.9.0+
Older Capabilities Still Relevant When Evaluating 3.1
- Native Apple Sign-In (November 2025, predates Core 3; import path changed)
useLocalCredentials()for biometric authentication password storage (August 2024)useSSO()replacing the deprecateduseOAuth()for browser-based OAuth@clerk/expo-passkeysfor FIDO2/WebAuthn passkeys (separate package, experimental)
Core 3 Foundation
Expo SDK 3.1 is built on Clerk's Core 3 platform release, which modernizes APIs, improves React compatibility, and delivers performance improvements across all Clerk SDKs. Every Expo developer upgrading to 3.x encounters these changes.
Package Rename
The package has been renamed from @clerk/clerk-expo to @clerk/expo, aligning with the @clerk/<framework> naming convention used across all Clerk SDKs (@clerk/nextjs, @clerk/react, @clerk/tanstack-start).
// Before (Core 2)
import { ClerkProvider } from '@clerk/clerk-expo'
// After (Core 3)
import { ClerkProvider } from '@clerk/expo'The legacy @clerk/clerk-expo package is deprecated as of the Core 3 launch. The npx @clerk/upgrade CLI handles this rename automatically.
The Core 3 Custom Flow API
Core 3 introduces a redesigned custom flow API (referred to as the "Signal API" in the March 9, 2026 changelog) that replaces the legacy setActive() pattern for custom flows built with useSignIn() and useSignUp(). The new API uses step methods like signIn.password() and signIn.emailCode.sendCode() instead of signIn.attemptFirstFactor(), and signIn.finalize() instead of setActive().
Legacy Pattern vs. Core 3 Custom Flow API
The following example demonstrates the Core 3 custom flow pattern for email and password sign-in:
import { useState } from 'react'
import { View, TextInput, Text, Button } from 'react-native'
import { useSignIn } from '@clerk/expo'
import { useRouter } from 'expo-router'
function SignInScreen() {
const { signIn, errors, fetchStatus } = useSignIn()
const router = useRouter()
const [identifier, setIdentifier] = useState('')
const [password, setPassword] = useState('')
const handleSignIn = async () => {
await signIn.create({ identifier })
await signIn.password({ password })
if (signIn.status === 'complete') {
await signIn.finalize({
navigate: ({ session }) => router.replace('/(home)'),
})
}
}
return (
<View>
<TextInput value={identifier} onChangeText={setIdentifier} />
{errors?.fields.identifier && <Text>{errors.fields.identifier.message}</Text>}
<TextInput value={password} onChangeText={setPassword} secureTextEntry />
<Button
title={fetchStatus === 'fetching' ? 'Signing in...' : 'Sign in'}
onPress={handleSignIn}
disabled={fetchStatus === 'fetching'}
/>
</View>
)
}For a comprehensive guide to migrating custom flows from setActive() to finalize(), see the SignInFuture API reference.
The <Show> Component
Core 3 consolidates <SignedIn>, <SignedOut>, and <Protect> into a single <Show> component with a when prop.
Before (Core 2):
import { SignedIn, SignedOut, Protect } from '@clerk/clerk-expo'
function AuthLayout() {
return (
<>
<SignedIn>
<HomeScreen />
</SignedIn>
<SignedOut>
<SignInScreen />
</SignedOut>
<Protect role="admin" fallback={<Text>Not authorized</Text>}>
<AdminPanel />
</Protect>
</>
)
}After (Core 3):
import { Show } from '@clerk/expo'
function AuthLayout() {
return (
<>
<Show when="signed-in">
<HomeScreen />
</Show>
<Show when="signed-out">
<SignInScreen />
</Show>
<Show when={{ role: 'admin' }} fallback={<Text>Not authorized</Text>}>
<AdminPanel />
</Show>
</>
)
}The when prop accepts 'signed-in', 'signed-out', { role: '...' }, { permission: '...' }, { feature: '...' }, { plan: '...' }, or a callback (has) => boolean. See the Show component reference for the full API.
Performance Improvements
Core 3 delivers a ~50KB gzipped bundle size reduction by sharing React internals across Clerk packages instead of duplicating them. Token refresh is now proactive: session tokens (60-second JWTs) are refreshed in the background approximately every 50 seconds, preventing mid-request delays that occurred when tokens expired during API calls.
Native UI Components
Version 3.1 introduces three prebuilt native components available from @clerk/expo/native. These components render with SwiftUI on iOS and Jetpack Compose on Android. These are truly native views, not WebView wrappers. They automatically synchronize authentication state with the JavaScript SDK, so a sign-in completed in native UI is immediately reflected in React hooks like useAuth().
All three components are currently in beta. They are powered by the clerk-ios and clerk-android native SDKs, which are added to your project automatically by the @clerk/expo Expo config plugin.
<AuthView />
<AuthView /> renders a complete native authentication interface. It handles all auth flows configured in the Clerk Dashboard: email, phone, OAuth, passkeys, multi-factor authentication (MFA), and password recovery.
A key advantage of <AuthView /> is that Google and Apple sign-in are handled automatically when those providers are enabled in the Dashboard. There is no need for useSignInWithGoogle(), expo-crypto, or any additional auth packages.
import { AuthView } from '@clerk/expo/native'
import { useAuth } from '@clerk/expo'
import { useEffect } from 'react'
import { useRouter } from 'expo-router'
export default function SignInScreen() {
const { isSignedIn } = useAuth({ treatPendingAsSignedOut: false })
const router = useRouter()
useEffect(() => {
if (isSignedIn) {
router.replace('/(home)')
}
}, [isSignedIn])
return <AuthView mode="signInOrUp" />
}See the AuthView reference for the full API.
<UserButton />
<UserButton /> displays the signed-in user's avatar (image or initials fallback). Tapping it opens the native profile management modal. The component accepts no props; the parent container controls its size and shape.
import { UserButton } from '@clerk/expo/native'
import { View } from 'react-native'
function Header() {
return (
<View style={{ width: 36, height: 36, borderRadius: 18, overflow: 'hidden' }}>
<UserButton />
</View>
)
}Sign-out actions in the profile modal are automatically synchronized with the JavaScript SDK. See the UserButton reference.
<UserProfileView />
<UserProfileView /> renders the complete user profile interface inline. It manages personal information, email addresses, phone numbers, MFA settings, passkeys, connected accounts, active sessions, and sign-out.
There are three usage patterns. The recommended approach is the native modal via the useUserProfileModal() hook:
import { UserProfileView } from '@clerk/expo/native'
import { useUserProfileModal } from '@clerk/expo'
import { Button, View } from 'react-native'
// Pattern 1: Native modal (recommended)
function ProfileButton() {
const { presentUserProfile, isAvailable } = useUserProfileModal()
return <Button title="Manage Profile" onPress={presentUserProfile} disabled={!isAvailable} />
}
// Pattern 2: Inline rendering
function ProfileScreen() {
return (
<View style={{ flex: 1 }}>
<UserProfileView style={{ flex: 1 }} />
</View>
)
}See the UserProfileView reference.
State Management with Hooks
Native components use hook-based state management rather than callbacks. A critical requirement when using native components is passing { treatPendingAsSignedOut: false } to useAuth().
The reason: native authentication has an asynchronous "pending" phase during native-to-JavaScript session synchronization. The default treatPendingAsSignedOut: true would prematurely evaluate the user as signed out during this sync, causing incorrect redirects.
import { useAuth } from '@clerk/expo'
import { useEffect } from 'react'
import { useRouter } from 'expo-router'
function AuthGate({ children }: { children: React.ReactNode }) {
const { isSignedIn, isLoaded } = useAuth({ treatPendingAsSignedOut: false })
const router = useRouter()
useEffect(() => {
if (isLoaded && !isSignedIn) {
router.replace('/sign-in')
}
}, [isLoaded, isSignedIn])
if (!isLoaded) return null
return <>{children}</>
}Web Fallback
Native components are iOS and Android only. For web builds in cross-platform Expo apps, use @clerk/expo/web which provides standard Clerk UI components (<SignIn />, <SignUp />, <UserButton />). Use React Native platform-specific file extensions (.ios.tsx, .android.tsx, .web.tsx) to separate native and web auth code. See the web support guide.
Native Sign-In
Native sign-in eliminates browser redirects for social authentication. Instead of opening a system browser for OAuth, the SDK uses platform-native APIs: ASAuthorization on iOS and Credential Manager on Android. The user stays inside the app, the credential picker is rendered by the operating system, and authentication completes faster.
This approach aligns with RFC 8252 (Section 8.12), which requires that native apps MUST NOT use embedded user-agents for OAuth and recommends system-level authentication surfaces.
Native Google Sign-In
Native Google Sign-In is new in 3.1. On iOS, it uses ASAuthorization (the system credential picker). On Android, it uses Credential Manager with one-tap and passkey-ready support. The integration is exposed via the NativeClerkGoogleSignIn TurboModule, bundled through the @clerk/expo config plugin.
For custom UI implementations, use useSignInWithGoogle() from @clerk/expo/google:
import { useSignInWithGoogle } from '@clerk/expo/google'
import { Button, Alert } from 'react-native'
function GoogleSignInButton() {
const { startGoogleAuthenticationFlow } = useSignInWithGoogle()
const handleGoogleSignIn = async () => {
try {
const { createdSessionId, setActive } = await startGoogleAuthenticationFlow()
if (createdSessionId) {
await setActive({ session: createdSessionId })
}
} catch (error) {
if (error.code === 'SIGN_IN_CANCELLED' || error.code === '-5') {
return // User cancelled
}
Alert.alert('Error', 'Google sign-in failed. Please try again.')
}
}
return <Button title="Sign in with Google" onPress={handleGoogleSignIn} />
}Requirements for custom hook usage:
- Peer dependency:
expo-crypto - Three OAuth client IDs configured in the Clerk Dashboard: iOS, Android, and Web (the Web client ID is required for token verification even in native-only apps)
- Environment variables:
EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID,EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID,EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME,EXPO_PUBLIC_CLERK_GOOGLE_ANDROID_CLIENT_ID - Development build required (not Expo Go)
See the useSignInWithGoogle reference and the Google Sign-In setup guide.
Native Apple Sign-In
Native Apple Sign-In predates 3.1. It was introduced in November 2025. It is included here because the import path changed in Core 3 and because it is part of the native sign-in story alongside the new Google Sign-In.
Apple Sign-In uses ASAuthorization on iOS. It is iOS only.
import { useSignInWithApple } from '@clerk/expo/apple'
import { Button, Alert, Platform } from 'react-native'
function AppleSignInButton() {
const { startAppleAuthenticationFlow } = useSignInWithApple()
if (Platform.OS !== 'ios') return null
const handleAppleSignIn = async () => {
try {
const { createdSessionId, setActive } = await startAppleAuthenticationFlow()
if (createdSessionId) {
await setActive({ session: createdSessionId })
}
} catch (error) {
if (error.code === 'ERR_REQUEST_CANCELED') {
return // User cancelled
}
Alert.alert('Error', 'Apple sign-in failed.')
}
}
return <Button title="Sign in with Apple" onPress={handleAppleSignIn} />
}Requirements:
- Peer dependencies:
expo-apple-authentication+expo-crypto - Expo config plugin option
appleSignIndefaults totrue - Development build required
- Works on iOS Simulator with limitations (no biometric); test on physical device for production flows
See the useSignInWithApple reference and the Apple Sign-In setup guide.
Import Path Changes
Both native sign-in hooks moved to dedicated entry points in Core 3 to avoid bundling optional native dependencies when they are not used:
// Before (Core 2)
import { useSignInWithApple, useSignInWithGoogle } from '@clerk/expo'
// After (Core 3)
import { useSignInWithApple } from '@clerk/expo/apple'
import { useSignInWithGoogle } from '@clerk/expo/google'The npx @clerk/upgrade CLI detects and fixes these imports automatically.
Browser-Based OAuth via useSSO()
For OAuth providers without native hooks (GitHub, Discord, LinkedIn, etc.) or enterprise SSO, useSSO() replaces the deprecated useOAuth(). The key difference: useOAuth() required the strategy at hook instantiation, while useSSO() accepts it at flow invocation via startSSOFlow({ strategy: 'oauth_github' }). This makes useSSO() a single hook for all browser-based OAuth and enterprise SSO providers. See the useSSO reference.
New Hooks and APIs
useUserProfileModal()
New in 3.1, this hook provides imperative control over the native profile modal. It returns:
presentUserProfile(): opens the native profile modal; resolves when dismissedisAvailable:booleanindicating whether the native SDK is ready (falseon web or without the config plugin)sessions: list of sessions registered on the device
import { useUserProfileModal } from '@clerk/expo'
import { Pressable, Text } from 'react-native'
function SettingsScreen() {
const { presentUserProfile, isAvailable } = useUserProfileModal()
return (
<Pressable onPress={presentUserProfile} disabled={!isAvailable}>
<Text>Manage Profile</Text>
</Pressable>
)
}useNativeSession() and useNativeAuthEvents()
Both hooks were announced in the March 9, 2026 changelog as newly exported hooks in 3.1. Neither hook has a dedicated reference page as of April 2026.
useNativeSession(): provides access to native SDK session management state (isSignedIn,sessionId,user,refresh()). For most use cases,useAuth()anduseSession()remain the recommended, fully documented hooks.useNativeAuthEvents(): listens for authentication state changes (signedIn,signedOut) from native components.
Use useAuth() and useSession() as the primary alternatives until dedicated reference documentation is available for these hooks.
useLocalCredentials()
useLocalCredentials() provides biometric sign-in for returning users by storing password credentials securely on-device, unlocked via Face ID or Touch ID. It is distinct from passkeys: useLocalCredentials() stores passwords behind biometrics, while @clerk/expo-passkeys implements true FIDO2/WebAuthn passkeys (a separate package, still experimental).
The hook returns:
- Name
hasCredentials- Type
boolean- Description
Whether credentials are stored on device
- Name
userOwnsCredentials- Type
boolean- Description
Whether stored credentials belong to the signed-in user
- Name
biometricType- Type
'face-recognition' | 'fingerprint' | null- Description
Available biometric type
- Name
setCredentials()- Type
(opts) => Promise- Description
Store credentials after successful sign-in
- Name
clearCredentials()- Type
() => Promise- Description
Remove stored credentials
- Name
authenticate()- Type
() => Promise<SignInResource>- Description
Trigger biometric prompt and sign in
import { useLocalCredentials, useSignIn } from '@clerk/expo'
import { Button, Text, View } from 'react-native'
function BiometricSignIn() {
const { hasCredentials, biometricType, authenticate, setCredentials } = useLocalCredentials()
const { signIn } = useSignIn()
if (hasCredentials && biometricType) {
return (
<View>
<Text>Sign in with {biometricType === 'face-recognition' ? 'Face ID' : 'Touch ID'}</Text>
<Button
title="Use biometrics"
onPress={async () => {
const result = await authenticate()
if (result.status === 'complete') {
// Session is active
}
}}
/>
</View>
)
}
// Fall back to password sign-in, then call setCredentials() on success
return <Text>No stored credentials — use password sign-in</Text>
}Requirements: expo-local-authentication + expo-secure-store. Device must have an enrolled biometric and passcode. Works only with password-based sign-in. Not supported on web. See the local credentials guide.
Choosing an Authentication Approach
With 3.1, Expo developers now have a clearer three-tier decision surface. Native components join the existing JavaScript-only and custom-UI-with-native-sign-in approaches that were available before 3.1.
JavaScript-Only is the approach with the broadest compatibility. You build custom UI with full control over authentication flows. OAuth is browser-based via useSSO(). This is the only approach that works with Expo Go (no development build required). Best for developers who want maximum UI customization or are prototyping.
JavaScript + Native Sign-In adds native Google and Apple sign-in buttons to a custom UI. Users authenticate through platform-native credential pickers with no browser redirect. Requires a development build because the native sign-in hooks depend on TurboModules that cannot run in Expo Go. Best for custom UI apps that want a native social provider experience.
Full Native Components uses the prebuilt <AuthView />, <UserButton />, and <UserProfileView /> components rendered in SwiftUI and Jetpack Compose. This is the fastest integration path: it requires the least code and handles all auth flows configured in the Dashboard automatically. A complete sign-in screen is a single <AuthView mode="signInOrUp" /> component with no hook wiring, no state management, and no OAuth configuration beyond the Dashboard. Requires a development build. Best for rapid authentication setup with a native look and feel.
Offline Support and Token Management
Token Caching with expo-secure-store
Clerk stores session tokens in memory by default, which means they are lost on app restart. For production apps, configure persistent token storage using the built-in tokenCache from @clerk/expo/token-cache. This is a drop-in solution backed by expo-secure-store (iOS Keychain, Android Keystore) that requires zero custom code:
import { ClerkProvider } from '@clerk/expo'
import { tokenCache } from '@clerk/expo/token-cache'
import { Slot } from 'expo-router'
export default function RootLayout() {
return (
<ClerkProvider
publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!}
tokenCache={tokenCache}
>
<Slot />
</ClerkProvider>
)
}Session tokens are 60-second JSON Web Tokens that are proactively refreshed every ~50 seconds in the background. See How Clerk Works for the full token lifecycle.
ClerkOfflineError
In Core 3, getToken() throws ClerkOfflineError when the device is offline instead of returning null. This is a breaking change that resolves a long-standing ambiguity: previously, null could mean either "the user is signed out" or "the device is offline and token refresh failed." Now, null unambiguously means signed out, and ClerkOfflineError means offline.
import { useAuth } from '@clerk/expo'
import { ClerkOfflineError } from '@clerk/react/errors'
function ApiClient() {
const { getToken } = useAuth()
const fetchData = async () => {
try {
const token = await getToken()
if (!token) {
// User is signed out
return
}
// Make authenticated request with token
} catch (error) {
if (ClerkOfflineError.is(error)) {
// Device is offline — show cached data or retry later
return
}
throw error
}
}
}Experimental Offline Mode
The __experimental_resourceCache option enables resilient initialization and cached token fallback during network outages:
import { ClerkProvider } from '@clerk/expo'
import { tokenCache } from '@clerk/expo/token-cache'
import { resourceCache } from '@clerk/expo/resource-cache'
import { Slot } from 'expo-router'
export default function RootLayout() {
return (
<ClerkProvider
publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!}
tokenCache={tokenCache}
__experimental_resourceCache={resourceCache}
>
<Slot />
</ClerkProvider>
)
}This caches environment config, client state, and session JWTs, enabling offline rendering of user info, role checks, and authenticated API calls with cached tokens. Write operations (sign-in, sign-up) still require network connectivity. This feature is experimental and not recommended as a production dependency. See the offline support guide.
Breaking Changes and Migration
This section covers the key breaking changes for Expo developers upgrading from @clerk/clerk-expo (Core 2) to @clerk/expo 3.x. For the full step-by-step migration walkthrough with code examples, see the Core 3 Upgrade Guide.
Using the Upgrade CLI
The fastest path to migration is the automated upgrade tool:
npx @clerk/upgradeThis CLI scans your codebase and applies AST-level transformations: it catches re-exports, aliased imports, and files across monorepo workspaces. It handles the package rename, import path updates, and component replacements automatically.
Breaking Changes Summary
Client Trust
Credential stuffing protection via Client Trust is an existing Clerk security feature, launched November 14, 2025. It is not a Core 3 or 3.1 addition, but Expo developers upgrading to Core 3 with custom password flows will encounter the needs_client_trust status for the first time if their app was created after the launch date or has opted in via the Dashboard.
Client Trust triggers when all three conditions are met: valid password entered, no MFA configured, and a new or unrecognized device. In the Core 3 custom flow API, handle it like this:
await signIn.password({ password })
if (signIn.status === 'needs_client_trust') {
// Check supported second factors for email code strategy
const emailCodeFactor = signIn.supportedSecondFactors?.find(
(factor) => factor.strategy === 'email_code',
)
if (emailCodeFactor) {
await signIn.mfa.sendEmailCode()
// After user enters the code:
await signIn.mfa.verifyEmailCode({ code: userEnteredCode })
}
}
if (signIn.status === 'complete') {
await signIn.finalize({ navigate: ({ session }) => router.replace('/(home)') })
}Client Trust is enabled by default for apps created after November 14, 2025. Existing apps must opt in via the Dashboard. See the Client Trust guide.
Migration Checklist
- Run
npx @clerk/upgrade(handles most codemods automatically) - Update Expo SDK to 53–55
- Verify package name updated:
@clerk/clerk-expo→@clerk/expo - Confirm
publishableKeyis explicit inClerkProvider - Update native sign-in hook import paths (
@clerk/expo/apple,@clerk/expo/google) - Replace
<SignedIn>/<SignedOut>/<Protect>with<Show> - Replace
Clerkexport withgetClerkInstance()oruseClerk() - Add
ClerkOfflineErrorhandling aroundgetToken()calls - Replace
setActive()withfinalize()in custom flows (not for OAuth hooks) - Handle
needs_client_trustin custom password sign-in flows - Test all authentication flows end-to-end
For the full migration walkthrough: Core 3 Upgrade Guide.
Implementation Notes
Plugin and Development Build
The @clerk/expo config plugin automatically adds the clerk-ios and clerk-android native SDKs to your project. Add it to app.json:
{
"expo": {
"plugins": [
[
"@clerk/expo",
{
"appleSignIn": true,
"keychainService": "my-app-keychain",
"theme": "./clerk-theme.json"
}
]
]
}
}The plugin accepts the following options:
Native components and native sign-in hooks require a development build. They can't run in Expo Go. Build with npx expo run:ios or npx expo run:android. Once built, JavaScript changes still hot-reload instantly. The JavaScript-only authentication approach works in Expo Go without a development build.
The optional theme JSON supports colors (14 hex tokens), darkColors, design.borderRadius, and design.fontFamily (iOS only). Changes to the theme file require npx expo prebuild --clean. See the theming reference.
Quick Start Example
The following example shows the fastest path to working authentication with native components. For the complete setup including ClerkProvider configuration, environment variables, Dashboard configuration, and native app registration, see the Expo Quickstart.
Prerequisites: Clerk account with Native API enabled, native app registered in Dashboard, Expo SDK 53–55, development build.
Install:
npx expo install @clerk/expo expo-secure-storeHome screen with signed-in state check and native <UserButton />:
import { UserButton } from '@clerk/expo/native'
import { Show, useAuth } from '@clerk/expo'
import { useEffect } from 'react'
import { useRouter } from 'expo-router'
import { View } from 'react-native'
export default function HomeScreen() {
const { isSignedIn, isLoaded } = useAuth({ treatPendingAsSignedOut: false })
const router = useRouter()
useEffect(() => {
if (isLoaded && isSignedIn === false) {
router.replace('/sign-in')
}
}, [isLoaded, isSignedIn])
return (
<Show when="signed-in">
<View style={{ flex: 1, alignItems: 'center', paddingTop: 60 }}>
<View style={{ width: 48, height: 48, borderRadius: 24, overflow: 'hidden' }}>
<UserButton />
</View>
</View>
</Show>
)
}Sign-in screen using the native <AuthView /> component:
import { AuthView } from '@clerk/expo/native'
export default function SignInScreen() {
return <AuthView mode="signInOrUp" />
}