Skip to main content

Expo Quickstart

There are three approaches for adding authentication to your Expo app.

ApproachAuth UIRequires dev buildBest for
Hosted authenticationIn-app browserNo (works in Expo Go)Fastest hosted setup
Native componentsPrebuilt native componentsYesPrebuilt native UI
Custom flowYour React Native componentsNo; native sign-in requires oneFull control over UI

Use the following tabs to choose your preferred approach:

Hosted authentication opens Account Portal in a browser authentication session. Account Portal supports the sign-in and sign-up methods enabled for your Clerk application.

Enable Native API

In the Clerk Dashboard, navigate to the Native applications page and enable the Native API. This is required to integrate Clerk in your native application or browser extension.

Warning

Enabling the Native API opens a public request pathway that bypasses browser-based CAPTCHA challenges. Learn more about how the Native API affects bot protection.

Create a new Expo app

If you don't already have an Expo app, run the following commands to create a new one.

npx create-expo-app@latest clerk-expo
cd clerk-expo
pnpm dlx create-expo-app@latest clerk-expo
cd clerk-expo
yarn dlx create-expo-app@latest clerk-expo
cd clerk-expo
bun x create-expo-app@latest clerk-expo
cd clerk-expo

Remove the starter routes

The default Expo template includes starter routes that aren't used in this guide. Remove them:

rm -f src/app/index.tsx src/app/explore.tsx

This guide replaces the starter src/app/_layout.tsx file and creates the app routes in later steps.

Note

If your Expo app uses a root app folder instead of src/app, use the same file paths without src/.

Install dependencies

Install the Clerk Expo SDK, secure token storage, and the Expo packages required to open the hosted browser session:

terminal
npx expo install @clerk/expo expo-secure-store expo-auth-session expo-crypto expo-web-browser
.env
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY

Verify app.json plugins

Expo automatically adds the required config plugins to your app.json file when you install the packages. Verify that @clerk/expo and expo-secure-store appear in the plugins array:

app.json
{
  "expo": {
    "ios": {
      "bundleIdentifier": "com.example.myapp"
    },
    "android": {
      "package": "com.example.myapp"
    },
    "plugins": ["expo-secure-store", "@clerk/expo"]
  }
}

Hosted authentication derives its default callback from these identifiers. On Android, the Clerk config plugin also registers the matching intent filter. Add the app on the Native applications page before you create a production build: the iOS bundle identifier, and the Android namespace and package name.

Add <ClerkProvider> to your root layout

The <ClerkProvider> component provides session and user context to Clerk's hooks and components. It's recommended to wrap your entire app at the entry point with <ClerkProvider> to make authentication globally accessible. See the reference docs for other configuration options.

Add the component to your root layout and pass your and tokenCache from @clerk/expo/token-cache as props, as shown in the following example:

src/app/_layout.tsx
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>
  )
}

Add hosted authentication

Create a src/app/index.tsx file. The following example opens Account Portal for sign-up. After authentication, the SDK updates useAuth() with the signed-in state.

src/app/index.tsx
import { useAuth } from '@clerk/expo'
import { useHostedAuth } from '@clerk/expo/hosted-auth'
import { ActivityIndicator, Button, StyleSheet, 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 (
      <View style={styles.container}>
        <ActivityIndicator size="large" />
      </View>
    )
  }

  return (
    <View style={styles.container}>
      {isSignedIn ? (
        <Text>You're signed in</Text>
      ) : (
        <Button title="Sign up" onPress={handleSignUp} />
      )}
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    gap: 12,
    alignItems: 'center',
    justifyContent: 'center',
  },
})

Run your project

Start the app in Expo Go or create a development build:

terminal
npx expo start

# Or create a development build
npx expo run:ios
npx expo run:android

Then use the terminal shortcuts to run the app on your preferred platform:

  • Press i to open the iOS simulator.
  • Press a to open the Android emulator.
  • Scan the QR code with Expo Go to run the app on a physical device.

Create your first user

Once the app opens on your device or simulator:

  • Open the sign-up flow.
  • Enter your details and complete the authentication flow.
  • After signing up, your first user will be created and you'll be signed in.

Beta

This feature is currently in beta. Functionality may change before general availability. If you run into any issues, please reach out to our support team.

This approach uses Clerk's prebuilt native componentsExpo Icon that render using SwiftUI on iOS and Jetpack Compose on Android. Choose it when you want authentication rendered with native components and can use a development build.

Enable Native API

In the Clerk Dashboard, navigate to the Native applications page and enable the Native API. This is required to integrate Clerk in your native application or browser extension.

Warning

Enabling the Native API opens a public request pathway that bypasses browser-based CAPTCHA challenges. Learn more about how the Native API affects bot protection.

Create a new Expo app

If you don't already have an Expo app, run the following commands to create a new one.

npx create-expo-app@latest clerk-expo
cd clerk-expo
pnpm dlx create-expo-app@latest clerk-expo
cd clerk-expo
yarn dlx create-expo-app@latest clerk-expo
cd clerk-expo
bun x create-expo-app@latest clerk-expo
cd clerk-expo

Remove the starter routes

The default Expo template includes starter routes that aren't used in this guide. Remove them:

rm -f src/app/index.tsx src/app/explore.tsx

This guide replaces the starter src/app/_layout.tsx file and creates the app routes in later steps.

Note

If your Expo app uses a root app folder instead of src/app, use the same file paths without src/.

Install dependencies

Install the required packages. Use npx expo install to ensure SDK-compatible versions.

  • The Clerk Expo SDKExpo Icon gives you access to prebuilt components, hooks, and helpers to make user authentication easier.
  • Clerk stores the active user's session token in memory by default. In Expo apps, the recommended way to store sensitive data, such as tokens, is by using expo-secure-store which encrypts the data before storing it.
  • expo-dev-client allows you to build and run your app in development mode.
npx expo install @clerk/expo expo-secure-store expo-dev-client
.env
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY

Verify app.json plugins

Expo automatically adds the required config plugins to your app.json file when you install the packages. Verify that @clerk/expo and expo-secure-store appear in the plugins array:

app.json
{
  "expo": {
    "plugins": ["expo-secure-store", "@clerk/expo"]
  }
}

Add <ClerkProvider> to your root layout

The <ClerkProvider> component provides session and user context to Clerk's hooks and components. It's recommended to wrap your entire app at the entry point with <ClerkProvider> to make authentication globally accessible. See the reference docs for other configuration options.

Add the component to your root layout and pass your and tokenCache from @clerk/expo/token-cache as props, as shown in the following example:

src/app/_layout.tsx
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>
  )
}

Add authentication and home screen

With native componentsExpo Icon, you can build a complete app in a single file. The <AuthView />Expo Icon component handles all sign-in and sign-up flows, and <UserButton />Expo Icon provides a profile avatar that opens the native user profile.

Create a src/app/index.tsx file with the following code. If the user is signed in, it displays the <UserButton />. If they're not signed in, it displays a Sign up button that opens the <AuthView />.

Important

When using native components, pass { treatPendingAsSignedOut: false } to useAuth() so pending are not treated as signed out.

Important

Keep the React Native <Modal> that contains <AuthView /> mounted at the same level as your signed-in and signed-out content. Don't render the modal only inside signed-out content, because auth state can change before required are finished and unmount the modal too early.

src/app/index.tsx
import { useAuth } from '@clerk/expo'
import { AuthView, UserButton } from '@clerk/expo/native'
import { useState } from 'react'
import { View, StyleSheet, ActivityIndicator, Button, Modal } from 'react-native'

export default function MainScreen() {
  const { isSignedIn, isLoaded } = useAuth({ treatPendingAsSignedOut: false })
  const [isAuthOpen, setIsAuthOpen] = useState(false)

  if (!isLoaded) {
    return (
      <View style={styles.centered}>
        <ActivityIndicator size="large" />
      </View>
    )
  }

  return (
    <View style={styles.container}>
      {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>
  )
}

const styles = StyleSheet.create({
  centered: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
})

Build and run

This approach requires a development build because it uses native modules. It cannot run in Expo Go.

terminal
# Using Expo CLI
npx expo run:ios
npx expo run:android

# Using EAS Build
eas build --platform ios
eas build --platform android

# Or using local prebuild
npx expo prebuild && npx expo run:ios --device
npx expo prebuild && npx expo run:android --device

Then use the terminal shortcuts to run the app on your preferred platform:

  • Press i to open the iOS simulator.
  • Press a to open the Android emulator.
  • Scan the QR code with Expo Go to run the app on a physical device.

Create your first user

Once the app opens on your device or simulator:

  • Open the sign-up flow.
  • Enter your details and complete the authentication flow.
  • After signing up, your first user will be created and you'll be signed in.

Configure social connections (optional)

<AuthView /> automatically shows sign-in buttons for any social connections enabled in your Clerk Dashboard. However, native OAuth requires additional credential setup — without it, the buttons will appear but fail with an error when tapped.

Sign in with Google

Follow the steps in the Sign in with Google guide to complete the following:

  1. Enable Google as a social connection with Use custom credentials toggled on.
  2. Create OAuth 2.0 credentials in the Google Cloud Console — you'll need an iOS Client ID, Android Client ID, and Web Client ID.
  3. Set the Web Client ID and Client Secret in the Clerk Dashboard.
  4. Add your iOS application to the Native Applications page in the Clerk Dashboard (Team ID + Bundle ID).
  5. Add your Android application to the Native Applications page in the Clerk Dashboard (package name).
  6. Add the Google Client IDs as environment variables in your .env file. Follow the .env.example in the Sign in with Google guide.
  7. Configure the @clerk/expo plugin with the iOS URL scheme in your app.json.

Important

You do not need to install expo-crypto or use the useSignInWithGoogle() hook — <AuthView /> handles the sign-in flow automatically.

Sign in with Apple

Follow the steps in the Sign in with Apple guide to complete the following:

  1. Add your iOS application to the Native Applications page in the Clerk Dashboard (Team ID + Bundle ID).
  2. Enable Apple as a social connection in the Clerk Dashboard.

Important

You do not need to install expo-apple-authentication, expo-crypto, or use the useSignInWithApple() hook — <AuthView /> handles the sign-in flow automatically.

This approach uses Clerk's APIs with your own React Native components and works in Expo Go — no dev build required.

Enable Native API

In the Clerk Dashboard, navigate to the Native applications page and enable the Native API. This is required to integrate Clerk in your native application or browser extension.

Warning

Enabling the Native API opens a public request pathway that bypasses browser-based CAPTCHA challenges. Learn more about how the Native API affects bot protection.

Create a new Expo app

If you don't already have an Expo app, run the following commands to create a new one.

npx create-expo-app@latest clerk-expo
cd clerk-expo
pnpm dlx create-expo-app@latest clerk-expo
cd clerk-expo
yarn dlx create-expo-app@latest clerk-expo
cd clerk-expo
bun x create-expo-app@latest clerk-expo
cd clerk-expo

Remove the starter routes

The default Expo template includes starter routes that aren't used in this guide. Remove them:

rm -f src/app/index.tsx src/app/explore.tsx

This guide replaces the starter src/app/_layout.tsx file and creates the app routes in later steps.

Note

If your Expo app uses a root app folder instead of src/app, use the same file paths without src/.

Install dependencies

Install the required packages. Use npx expo install to ensure SDK-compatible versions.

  • The Clerk Expo SDKExpo Icon gives you access to prebuilt components, hooks, and helpers to make user authentication easier.
  • Clerk stores the active user's session token in memory by default. In Expo apps, the recommended way to store sensitive data, such as tokens, is by using expo-secure-store which encrypts the data before storing it.
npx expo install @clerk/expo expo-secure-store
.env
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY

Add <ClerkProvider> to your root layout

The <ClerkProvider> component provides session and user context to Clerk's hooks and components. It's recommended to wrap your entire app at the entry point with <ClerkProvider> to make authentication globally accessible. See the reference docs for other configuration options.

Add the component to your root layout and pass your and tokenCache from @clerk/expo/token-cache as props, as shown in the following example:

src/app/_layout.tsx
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>
  )
}

Add a sign-up screen

Create a src/app/index.tsx file. The following example uses the useSignUp() hook to build a basic email and password sign-up form. Clerk emails the user a verification code, so the screen shows a code field once the sign-up starts.

src/app/index.tsx
import { useAuth, useSignUp } from '@clerk/expo'
import { useState } from 'react'
import { Button, StyleSheet, 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) {
      // Handle the error in your app.
      // See https://clerk.com/docs/guides/development/custom-flows/error-handling
      return
    }

    const { error: sendError } = await signUp.verifications.sendEmailCode()
    if (sendError) {
      // Handle the error in your app.
      return
    }

    setIsVerifying(true)
  }

  const handleVerify = async () => {
    const { error } = await signUp.verifications.verifyEmailCode({ code })
    if (error) {
      // Handle the error in your app.
      return
    }

    const { error: finalizeError } = await signUp.finalize()
    if (finalizeError) {
      // Handle the error in your app.
    }
  }

  if (!isLoaded) {
    return null
  }

  if (isSignedIn) {
    return (
      <View style={styles.container}>
        <Text>You're signed in</Text>
      </View>
    )
  }

  if (isVerifying) {
    return (
      <View style={styles.container}>
        <TextInput
          style={styles.input}
          value={code}
          placeholder="Enter your verification code"
          onChangeText={setCode}
          keyboardType="numeric"
        />
        <Button title="Verify" onPress={handleVerify} />
      </View>
    )
  }

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        autoCapitalize="none"
        value={emailAddress}
        placeholder="Enter email"
        onChangeText={setEmailAddress}
        keyboardType="email-address"
      />
      <TextInput
        style={styles.input}
        value={password}
        placeholder="Enter password"
        secureTextEntry={true}
        onChangeText={setPassword}
      />
      <Button title="Sign up" onPress={handleSignUp} />
      {/* Required for sign-up flows on Expo web. Clerk skips the browser CAPTCHA on iOS and Android */}
      <View nativeID="clerk-captcha" />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    gap: 12,
    justifyContent: 'center',
  },
  input: {
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
  },
})

When verifyEmailCode() completes the sign-up, finalize() converts it into an active session and updates useAuth() with the signed-in state.

Run your project

Run your project with the following command:

npx expo start

Then use the terminal shortcuts to run the app on your preferred platform:

  • Press i to open the iOS simulator.
  • Press a to open the Android emulator.
  • Scan the QR code with Expo Go to run the app on a physical device.

Create your first user

Once the app opens on your device or simulator:

  • Open the sign-up flow.
  • Enter your details and complete the authentication flow.
  • After signing up, your first user will be created and you'll be signed in.

For complete sign-up and sign-in flows with guided comments and error handling, see the Build a custom email/password authentication flow guide. To use other authentication methods, such as passwordless or OAuth, see the . To add native Sign in with Google or Sign in with Apple buttons, see the Sign in with Google and Sign in with Apple guides. These use native modules, so they require a development build and cannot run in Expo Go. The Expo SDK referenceExpo Icon lists the hooks and helpers available when building custom flows.

Enable OTA updates

Though not required, it is recommended to implement over-the-air (OTA) updates in your Expo app. This enables you to easily roll out Clerk's feature updates and security patches as they're released without having to resubmit your app to mobile marketplaces.

See the expo-updates library to learn how to get started.

Next steps

Explore the most relevant next steps for your SDK using the following guides.

Prebuilt native componentsBeta

Learn how to quickly add authentication to your app using Clerk's prebuilt native UI for iOS and Android.

Build custom flows

Learn how to build custom user interfaces entirely from scratch using the Clerk API.

Read user data

Learn how to use Clerk's hooks and helpers to read user data in your Expo app.

Deploy an Expo app to production

Learn how to deploy your Expo app to production.

Feedback

What did you think of this content?

Last updated on