This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
Using Clerk
Edit page
Learn how to add Clerk authentication and user management in your Expo and React Native projects.
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, hosted authentication, 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 4.x, which supports Expo SDK 54 and later.
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 |
|---|---|---|---|
| Hosted authentication | A button that opens Clerk's Account Portal in a browser authentication session | The fastest setup, with every method enabled in your dashboard | |
| Native UI components | Drop in <AuthView />, <UserButton />, and <UserProfileView /> from @clerk/expo/native | A complete native sign-in and account management UI | |
| Custom flow | Your own React Native screens that call hooks such as useSignUp() and useSignIn() | Maximum UI control |
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 UI components and the native sign-in hooks require a development build. The hosted authentication and custom flow approaches also work 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.
For hosted authentication, also install the packages Clerk uses to open the browser authentication session:
- npx expo install expo-auth-session expo-crypto expo-web-browserIf you plan to add native Sign in with Google buttons to a custom flow, install @clerk/expo-google-signin and expo-crypto:
- npx expo install @clerk/expo-google-signin expo-cryptoFor native Sign in with Apple buttons, 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
Verify the config plugins
Add @clerk/expo and expo-secure-store to the plugins array in your app config. If your project uses a static app.json and you installed the packages with npx expo install, Expo has already added them:
{ "expo": { "plugins": ["expo-secure-store", "@clerk/expo"] } }
The @clerk/expo plugin adds the Apple Sign In entitlement (disable it with the appleSignIn: false plugin option if your app doesn't use it), registers the Android intent filter for the hosted authentication callback, and applies the Android packaging fixes required by the underlying clerk-android SDK. If you use the native Sign in with Google buttons, also add the @clerk/expo-google-signin plugin alongside it.
Hosted authentication derives its default callback from the android.package and ios.bundleIdentifier values in your app config. Before you create a production build, add the app on the Native applications page in the Clerk Dashboard with the same Android package name and iOS bundle identifier, since production instances validate the callback against the registered values.
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.
Add authentication
The next step depends on which approach you chose. The tabs below show the minimum code for each.
Hosted authentication opens Clerk's Account Portal in a browser authentication session over your app. Users can complete any sign-in or sign-up method enabled for your Clerk application, and the SDK activates the resulting session in your app. Call startHostedAuth() from the useHostedAuth() hook:
import { useAuth } from '@clerk/expo'; import { useHostedAuth } from '@clerk/expo/hosted-auth'; import { ActivityIndicator, Button, Text, View } from 'react-native'; export default function MainScreen() { const { isLoaded, isSignedIn } = useAuth(); const { startHostedAuth } = useHostedAuth(); const handleSignUp = async () => { try { await startHostedAuth({ mode: 'sign-up' }); } catch (error) { // Handle the error in your app } }; if (!isLoaded) { return <ActivityIndicator size="large" />; } return ( <View> {isSignedIn ? ( <Text>You're signed in</Text> ) : ( <Button title="Sign up" onPress={handleSignUp} /> )} </View> ); }
After authentication completes, the SDK closes the browser session, activates the new session, and updates useAuth() with the signed-in state. The browser doesn't retain a separate active session.
startHostedAuth() opens the sign-in page by default and accepts mode: 'sign-in' | 'sign-up'. It resolves with a null createdSessionId when the user dismisses the browser without finishing, and throws when authentication fails.
This approach works in Expo Go, where Expo supplies a development callback. In a development or production build, the callback is derived from your iOS bundle identifier or Android package name, so keep the @clerk/expo config plugin in your app config and rebuild the native project after changing either identifier.
Account Portal runs in a browser, so social sign-in uses each provider's web OAuth flow rather than the native one. See the hosted authentication guide for production credential requirements and troubleshooting.
<AuthView /> 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. It renders inline in your React Native view hierarchy, so you can place it in a modal, a route, or a full-screen view. The following example opens it in a modal:
import { useAuth } from '@clerk/expo'; import { AuthView, UserButton } from '@clerk/expo/native'; import { useState } from 'react'; import { ActivityIndicator, Button, Modal, View } from 'react-native'; export default function MainScreen() { const { isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false }); const [isAuthOpen, setIsAuthOpen] = useState(false); if (!isLoaded) { return <ActivityIndicator size="large" />; } return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> {isSignedIn ? <UserButton /> : <Button title="Sign up" onPress={() => setIsAuthOpen(true)} />} <Modal animationType="slide" visible={isAuthOpen} presentationStyle="pageSheet" onRequestClose={() => setIsAuthOpen(false)}> <AuthView onDismiss={() => setIsAuthOpen(false)} /> </Modal> </View> ); }
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 pending session tasks are not treated as signed out.
Keep the<Modal>that contains<AuthView />mounted at the same level as your signed-in and signed-out content. If you render it only inside signed-out content, auth state can change while session tasks are still pending, and your conditional render unmounts the modal too early.
<AuthView /> accepts mode="signIn" | "signUp" | "signInOrUp" (the default), an isDismissible boolean that controls the native dismiss button, and an onDismiss callback. When users must authenticate before continuing, render it as a full-screen view with isDismissible={false} instead of in a modal.
<UserButton /> takes no props. It displays the signed-in user's profile image or initials and opens the native <UserProfileView /> when tapped, where users can manage personal information, security settings, and sign out.
<AuthView /> automatically shows sign-in buttons for any social connections enabled in the Clerk Dashboard and handles the flows internally, so you don't need expo-crypto or the native sign-in hooks. Native OAuth still requires credential setup in the Clerk Dashboard and provider consoles. Otherwise, the buttons appear but fail when tapped. Follow the Clerk Sign in with Google and Sign in with Apple guides linked at the bottom of this page.
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 developmentBuild your own screens with the Core 3 hooks. This works in Expo Go. The following example uses useSignUp() to build an email and password sign-up form with email code verification:
import { useAuth, useSignUp } from '@clerk/expo'; import { useState } from 'react'; import { Button, Text, TextInput, View } from 'react-native'; export default function MainScreen() { const { isLoaded, isSignedIn } = useAuth(); const { signUp } = useSignUp(); const [emailAddress, setEmailAddress] = useState(''); const [password, setPassword] = useState(''); const [code, setCode] = useState(''); const [isVerifying, setIsVerifying] = useState(false); const handleSignUp = async () => { const { error } = await signUp.password({ emailAddress, password }); if (error) { console.error(JSON.stringify(error, null, 2)); return; } const { error: sendError } = await signUp.verifications.sendEmailCode(); if (sendError) { console.error(JSON.stringify(sendError, null, 2)); return; } setIsVerifying(true); }; const handleVerify = async () => { const { error } = await signUp.verifications.verifyEmailCode({ code }); if (error) { console.error(JSON.stringify(error, null, 2)); return; } await signUp.finalize(); }; if (!isLoaded) { return null; } if (isSignedIn) { return <Text>You're signed in</Text>; } if (isVerifying) { return ( <View> <TextInput value={code} placeholder="Enter your verification code" onChangeText={setCode} keyboardType="numeric" /> <Button title="Verify" onPress={handleVerify} /> </View> ); } return ( <View> <TextInput autoCapitalize="none" value={emailAddress} placeholder="Enter email" onChangeText={setEmailAddress} keyboardType="email-address" /> <TextInput value={password} placeholder="Enter password" secureTextEntry onChangeText={setPassword} /> <Button title="Sign up" onPress={handleSignUp} /> {/* Required for sign-up flows on Expo web. Clerk skips the browser CAPTCHA on Android and iOS */} <View nativeID="clerk-captcha" /> </View> ); }
In Core 3, methods such as signUp.password() and signUp.verifications.verifyEmailCode() return { error } instead of throwing for validation errors. When verification completes the sign-up, signUp.finalize() converts it into an active session and updates useAuth() with the signed-in state.
The corresponding sign-in flow uses useSignIn(), where finalize() accepts a navigate callback so you can handle session tasks before redirecting:
import { useSignIn } from '@clerk/expo'; import { type Href, useRouter } from 'expo-router'; export default function SignInScreen() { const { signIn } = useSignIn(); const router = useRouter(); const handleSignIn = async (emailAddress: string, password: string) => { const { error } = await signIn.password({ emailAddress, password }); if (error) { 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); }, }); } }; // ... render your email and password fields }
To add native Sign in with Google and Sign in with Apple buttons to your custom screens, use the useSignInWithGoogle() hook from @clerk/expo/google and the useSignInWithApple() hook from @clerk/expo/apple. Both return a start method (startGoogleAuthenticationFlow() and startAppleAuthenticationFlow()) that resolves with { createdSessionId, setActive }.
Both hooks use native modules, so they require a development build and the packages from the install step. useSignInWithGoogle() also needs the @clerk/expo-google-signin config plugin. On iOS, App Store Guideline 4.8 requires that any app offering third-party social sign-in must also offer Sign in with Apple. Follow the Clerk guides linked at the bottom of this page to register your credentials in the Clerk Dashboard and the provider consoles.
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
For the hosted authentication and custom flow approaches, run the following command and open the project in Expo Go:
- npx expo start- npx expo run:android- npx expo run:iosNext steps
Step-by-step instructions for setting up each of the three integration approaches, with companion repositories on GitHub.
Use Clerk's Account Portal to sign users in and up from your Expo app, with callbacks, cancellation handling, and production setup.
API reference for AuthView, UserButton, and UserProfileView, including configuration, theming, and platform requirements.
Set up native Sign in with Google for Android and iOS with the Clerk Dashboard and the Google Cloud Console.
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.