Using Clerk
Edit page
Learn how to add Clerk authentication and user management in your Expo and React Native projects.
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
Clerk is an authentication and user management platform that provides sign-up, sign-in, multi-factor authentication, social sign-in, organizations, and a hosted user database. The @clerk/expo SDK gives you React hooks, control components, and prebuilt native UI components that render with Jetpack Compose on Android and SwiftUI on iOS.
This guide shows you how to install @clerk/expo, wrap your app in <ClerkProvider>, and choose the integration approach that fits your project. It targets @clerk/expo Core 3 (the 3.x release line), which supports Expo SDK 53, 54, and 55.
Choose your integration approach
@clerk/expo supports three approaches. Pick the one that matches your needs — you can change later without rewriting your app.
| Approach | What you build | Runs in Expo Go | Best for |
|---|---|---|---|
| JavaScript only | Your own React Native screens that call useSignIn() and useSignUp() | Maximum UI control, prototyping in Expo Go | |
| JavaScript with native sign-in | Your own screens plus native Sign in with Google and Sign in with Apple buttons | Apps that want a custom look but native social sign-in | |
| Native UI components | Drop in <AuthView />, <UserButton />, and <UserProfileView /> from @clerk/expo/native | The fastest path to a complete sign-in and account management UI |
The native UI components in@clerk/expo/nativeare currently in beta. They render with Jetpack Compose on Android and SwiftUI on iOS and they synchronize the signed-in session back to the JavaScript SDK so all@clerk/expohooks (such asuseAuth()anduseUser()) stay in sync.
Prerequisites
4 requirements
4 requirements
1.
Sign up at the Clerk Dashboard and create an application.
2.
Open the Native applications page in the Clerk Dashboard and ensure Native API is on. This is required for any Expo integration that uses @clerk/expo.
3.
@clerk/expo Core 3 has a peer dependency of expo: >=53 <56.
4.
The native sign-in and native UI component approaches require a development build. The JavaScript-only approach also works in Expo Go.
Install and configure Clerk
1
Install @clerk/expo and expo-secure-store
Use npx expo install so versions match your Expo SDK:
- npx expo install @clerk/expo expo-secure-storeexpo-secure-store is a peer dependency. Clerk uses it through @clerk/expo/token-cache to encrypt session tokens with the iOS Keychain and the Android Keystore.
If you plan to use native Sign in with Google, also install expo-crypto:
- npx expo install expo-cryptoFor native Sign in with Apple, install both expo-apple-authentication and expo-crypto:
- npx expo install expo-apple-authentication expo-cryptoYou do not need any of these extra packages if you only use <AuthView /> from @clerk/expo/native, since the component handles social sign-in flows internally.
2
Add the Clerk config plugin
Add @clerk/expo to the plugins array in your app config:
{ "expo": { "plugins": ["@clerk/expo"] } }
The plugin configures the iOS URL scheme for native Sign in with Google (when EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME is set) and applies the Android packaging fixes required by the underlying clerk-android SDK. The Apple Sign In entitlement is added by the plugin from expo-apple-authentication library, if you install it.
3
Add your Clerk Publishable Key
Copy your Publishable Key from the API keys page in the Clerk Dashboard, then add it to a .env file in the root of your project:
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your-key-here
The EXPO_PUBLIC_ prefix is required because Expo inlines these values at build time so they are available in your JavaScript bundle. Clerk's Publishable Key is safe to expose. Do not put Secret Keys behind the EXPO_PUBLIC_ prefix.
4
Wrap your app in <ClerkProvider>
In your root layout file (src/app/_layout.tsx with Expo Router), wrap your app in <ClerkProvider> and pass the Publishable Key. Passing tokenCache explicitly is recommended:
import { ClerkProvider } from '@clerk/expo'; import { tokenCache } from '@clerk/expo/token-cache'; import { Slot } from 'expo-router'; const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!; if (!publishableKey) { throw new Error('Add your Clerk Publishable Key to the .env file'); } export default function RootLayout() { return ( <ClerkProvider publishableKey={publishableKey} tokenCache={tokenCache}> <Slot /> </ClerkProvider> ); }
In Core 3, publishableKey is required on <ClerkProvider> for Expo apps. Environment variables inside node_modules are not inlined during production React Native builds, so the prop must be passed explicitly.
tokenCache from @clerk/expo/token-cache persists the user's session across app restarts using expo-secure-store. Passing it explicitly makes the dependency clear and lets you swap in a custom cache implementation later.
Build your sign-in screen
The next step depends on which approach you chose. The tabs below show the minimum code for each.
Drop <AuthView /> into a screen. It renders a complete native sign-in and sign-up interface that handles email, phone, passkeys, multi-factor authentication, and any social connection enabled in the Clerk Dashboard:
import { AuthView } from '@clerk/expo/native'; import { useAuth } from '@clerk/expo'; import { useRouter } from 'expo-router'; import { useEffect } from 'react'; export default function SignInScreen() { const { isSignedIn } = useAuth({ treatPendingAsSignedOut: false }); const router = useRouter(); useEffect(() => { if (isSignedIn) { router.replace('/(home)'); } }, [isSignedIn]); return <AuthView mode="signInOrUp" />; }
After the user signs in, the native session is synchronized back to the JavaScript SDK, so useAuth() and useUser() reflect the signed-in state. Pass treatPendingAsSignedOut: false to useAuth() so the brief native-to-JS session sync isn't reported as a signed-out state, which can otherwise cause redirect loops on screens that key navigation off isSignedIn.
<AuthView /> accepts mode="signIn" | "signUp" | "signInOrUp" and an optional isDismissable boolean.
To show the user's avatar and a profile modal elsewhere in your app, use <UserButton />:
import { UserButton } from '@clerk/expo/native'; import { Show } from '@clerk/expo'; import { View } from 'react-native'; export function Header() { return ( <Show when="signed-in"> <View style={{ width: 36, height: 36, borderRadius: 18, overflow: 'hidden' }}> <UserButton /> </View> </Show> ); }
<UserButton /> fills its parent, so the parent controls size and shape. To open the native profile modal from any other UI, use the useUserProfileModal() hook:
import { useUserProfileModal } from '@clerk/expo'; import { Pressable, Text } from 'react-native'; export function ProfileLink() { const { presentUserProfile, isAvailable } = useUserProfileModal(); return ( <Pressable onPress={presentUserProfile} disabled={!isAvailable}> <Text>Manage profile</Text> </Pressable> ); }
This approach requires a development build because the components are backed by native modules:
# Run a development build locally- npx expo run:android- npx expo run:ios# Or build with EAS- eas build --platform ios --profile developmentUse the useSignInWithGoogle() and useSignInWithApple() hooks alongside your own React Native UI:
import { useSignInWithGoogle } from '@clerk/expo/google'; import { useRouter } from 'expo-router'; import { Platform, Text, TouchableOpacity } from 'react-native'; export function GoogleSignInButton() { const { startGoogleAuthenticationFlow } = useSignInWithGoogle(); const router = useRouter(); if (Platform.OS !== 'ios' && Platform.OS !== 'android') return null; const onPress = async () => { try { const { createdSessionId, setActive } = await startGoogleAuthenticationFlow(); if (createdSessionId && setActive) { await setActive({ session: createdSessionId }); router.replace('/'); } } catch (err) { console.error('Google sign-in error', err); } }; return ( <TouchableOpacity onPress={onPress}> <Text>Continue with Google</Text> </TouchableOpacity> ); }
On Android, this uses Credential Manager and never opens a browser. On iOS, the flow uses ASAuthorization (the system credential picker) when EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME is configured — without it, iOS falls back to a system browser sheet. Follow the Clerk guides linked at the bottom of this page to register your Android package name, iOS bundle ID, and SHA-256 fingerprints in the Clerk Dashboard and the Google Cloud Console.
useSignInWithApple() from @clerk/expo/apple follows the same pattern (startAppleAuthenticationFlow() returning { createdSessionId, setActive }) and is iOS only. App Store Guideline 4.8 requires that any app offering third-party social sign-in must also offer Sign in with Apple on iOS.
This approach requires a development build because it uses native modules. useSignInWithGoogle() requires expo-crypto. The useSignInWithApple() requires both expo-apple-authentication and expo-crypto.
Build a custom sign-in form using the Core 3 hooks. This works in Expo Go.
import { useSignIn } from '@clerk/expo'; import { useRouter, type Href } from 'expo-router'; import { useState } from 'react'; import { Text, TextInput, TouchableOpacity, View } from 'react-native'; export default function SignInScreen() { const { signIn, fetchStatus, errors } = useSignIn(); const router = useRouter(); const [emailAddress, setEmailAddress] = useState(''); const [password, setPassword] = useState(''); const onSignInPress = async () => { const { error } = await signIn.password({ emailAddress, password }); if (error) { console.error(JSON.stringify(error, null, 2)); return; } if (signIn.status === 'complete') { await signIn.finalize({ navigate: ({ session, decorateUrl }) => { if (session?.currentTask) return; // let the session task layer handle it router.replace(decorateUrl('/') as Href); }, }); } }; return ( <View> <TextInput autoCapitalize="none" value={emailAddress} placeholder="Email" onChangeText={setEmailAddress} /> <TextInput value={password} placeholder="Password" secureTextEntry onChangeText={setPassword} /> <TouchableOpacity onPress={onSignInPress} disabled={fetchStatus === 'fetching'}> <Text>Sign in</Text> </TouchableOpacity> {errors?.fields?.identifier ? <Text>{errors.fields.identifier.message}</Text> : null} {errors?.fields?.password ? <Text>{errors.fields.password.message}</Text> : null} </View> ); }
In Core 3, signIn.password() returns { error } instead of throwing for validation errors, and signIn.finalize() replaces the legacy setActive() call for sign-in flows built with useSignIn().
The corresponding sign-up flow with email verification looks like:
await signUp.password({ emailAddress, password }); await signUp.verifications.sendEmailCode(); // ... collect the code from the user, then: await signUp.verifications.verifyEmailCode({ code }); if (signUp.status === 'complete') { await signUp.finalize({ navigate: ({ session, decorateUrl }) => { if (session?.currentTask) return; router.replace(decorateUrl('/') as Href); }, }); }
Clerk's bot sign-up protection is enabled by default, so include <View nativeID="clerk-captcha" /> somewhere in your sign-up screen so the invisible CAPTCHA can mount.
Read the signed-in user
Anywhere in your app, use useUser() and useAuth() to read user data, plus <Show> and useClerk() to protect content and sign out:
import { Show, useClerk, useUser } from '@clerk/expo'; import { Link } from 'expo-router'; import { Pressable, Text, View } from 'react-native'; export default function HomeScreen() { const { user } = useUser(); const { signOut } = useClerk(); return ( <View> <Show when="signed-in"> <Text>Hello, {user?.firstName ?? 'friend'}</Text> <Pressable onPress={() => signOut()}> <Text>Sign out</Text> </Pressable> </Show> <Show when="signed-out"> <Link href="/(auth)/sign-in"> <Text>Sign in</Text> </Link> </Show> </View> ); }
<Show> replaces the legacy <SignedIn>, <SignedOut>, and <Protect> components from earlier versions of the SDK. It also accepts when={{ role: '...' }}, when={{ permission: '...' }}, and other authorization predicates.
Run the app
- npx expo run:android- npx expo run:iosFor the JavaScript-only approach, run the following command and open the project in Expo Go:
- npx expo startNext steps
Step-by-step instructions for setting up each of the three integration approaches, with companion repositories on GitHub.
API reference for AuthView, UserButton, and UserProfileView, including configuration, theming, and platform requirements.
Set up native Sign in with Google for Android and iOS using ASAuthorization and Credential Manager.
Set up native Sign in with Apple to satisfy App Store Guideline 4.8.
Use Clerk's hooks and Show component to protect routes and access user data in your Expo app.
Configure production credentials, allowlist mobile SSO redirects, and ship with EAS Build.