AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
Clerk-expo provides a comprehensive suite of tools for managing user authentication in Expo and React Native projects. It supports both prebuilt native components for rapid implementation and custom flow hooks for tailored UI experiences. The skill covers essential authentication features including email/password, OTP, MFA, OAuth/SSO, and biometric login, while ensuring proper session management through secure token caching and Expo Router integration.
What it does
- Provides prebuilt native components like AuthView and UserButton for rapid UI integration.
- Supports custom authentication flows using hooks for full UI control.
- Handles complex auth scenarios including MFA, social login, and biometric authentication.
- Integrates with Expo Router for protected route management.
- Manages secure session persistence via token caching.
- Enables bot protection via native captcha mount points.
When to use it
- When adding user authentication to an Expo or React Native application.
- When you need to implement social login (Google, Apple) or passwordless flows.
- When you require a secure, managed authentication system that supports MFA and biometrics.
- When you need to protect specific routes or screens based on user authentication status.
Advantages
- Rapid development with prebuilt native components.
- Flexible architecture supporting both standard and custom UI requirements.
- Automatic session synchronization between native and JavaScript layers.
- Comprehensive support for modern auth standards like MFA and SSO.
Usage examples
- “How do I add Clerk authentication to my new Expo project?”
- The agent will guide you through the setup process, including installing the package, configuring the ClerkProvider with your publishable key, and implementing the AuthView component.
- “I need to implement a custom sign-in screen with email and password.”
- The agent will provide a code example using the useSignIn hook, demonstrating how to handle the sign-in state, errors, and the finalize method.
- “How can I protect my dashboard route in Expo Router?”
- The agent will show you how to use the useAuth hook to check the user's signed-in status and redirect them to a sign-in screen if they are unauthenticated.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/clerk/clerk-expo/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip clerk-expo.zip -d .agents/skills
Documentation by clerk
Clerk Expo (React Native)
Implement Clerk in Expo / React Native projects. This skill inlines verified patterns for the stable surface (provider, token cache, flows) and requires source inspection of the installed @clerk/expo package for anything volatile (component props, hook signatures).
Activation Rules
Activate when either is true:
The user asks for auth in an Expo or React Native app, or mentions
@clerk/expo,ClerkProvider, Expo Router auth, or Clerk hooks in a native app.The project is Expo/React Native (
app.json/app.config.js,expoinpackage.json,metro.config.js,@clerk/expodependency).
Route away when:
Native iOS/Swift project (
.xcodeproj,Package.swift) →clerk-swiftNative Android/Kotlin project (
build.gradlewithout React Native) →clerk-androidWeb-only framework (Next.js, Remix, plain React, etc.) → the matching framework skill
Intent Map
Match what the user asked for, then load the reference(s) listed. Load only what the task needs.
| User intent (examples) | Path | Reference |
|---|---|---|
| "Add auth to my app" / "add sign-in with Clerk" | Prebuilt native components (default) | references/setup.md + references/prebuilt-components.md |
| "Add auth" but Expo Go / web / custom UI required | Custom flows | references/setup.md + references/custom-flows.md |
| "Add phone / SMS auth", "email OTP", "passwordless" | Custom flow, phoneCode / emailCode | references/custom-flows.md |
| "Sign in with Google/Apple/GitHub", "social login", "SSO" | Browser SSO or native buttons | references/sso-and-native-auth.md |
| "MFA / 2FA / TOTP", "forgot password" | Custom flow additions | references/custom-flows.md |
| "Email link" | Expo web custom flow; native apps use email codes for custom UI or <AuthView /> in a development build | references/custom-flows.md + references/prebuilt-components.md |
| "Protect routes/screens", "redirect if signed out" | Expo Router guards | references/protected-routes.md |
| "Show user profile", "org switching", "push notifications", "sign out", "call my backend" | App recipes | references/recipes.md |
| "Biometric login", "Face ID", "passkeys" | Device features | references/recipes.md |
Default Path Decision
When the user says "add auth" without specifying UI:
Default to prebuilt native components (
AuthView+UserButtonfrom@clerk/expo/native). Fastest to working auth; UI is maintained by Clerk. Tell the developer they are in beta and require a development build.Fall back to custom flows when any of these hold — say why when you switch:
The project must run in Expo Go (no dev build).
The app targets web (native components don't render on web).
The developer wants their own UI or a specific brand experience beyond theming.
If the developer has an existing auth UI, extend what's there — don't rip out custom flows to insert
AuthView(or vice versa) without being asked.
Do not blend prebuilt components and custom flows for the same auth step (e.g. AuthView plus a custom password form). Blending is allowed only when the developer explicitly asks.
Quick Workflow
Confirm project type (Expo/RN) and pick the path per the Intent Map / Default Path rules.
For initial application setup, existing-app linking, or missing keys, follow the canonical clerk-setup skill. Don't start a separate
initflow here. Then follow the Expo setup reference for provider, token cache, config plugin, and build-type steps not already completed.Verify dashboard prerequisites (Gate 2 and Gate 3 below).
Implement from the selected reference only.
Verify by building, not just by writing:
Run the project's typecheck (
npx tsc --noEmitor equivalent).Build and launch:
npx expo run:ios/run:androidfor native features,npx expo startfor Expo Go flows. If the build fails, fix and rebuild iteratively — build errors against the installed SDK are the ground truth when this skill and the SDK disagree. After ~5 failed fix attempts, stop and ask the developer how to proceed instead of thrashing.Walk the developer through one real sign-in, then confirm the session survives an app restart (token cache working).
Execution Gates (Do Not Skip)
Publishable key — Read from
process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEYin app code. NeverNEXT_PUBLIC_, never hardcoded. If the key is missing, follow the appropriate new or existing application path in clerk-setup. Expo has no accountless keys, and an agent'sinitwon't write one without a target application, even when signed in. For a new application, have the developer sign in and select or create an application, then runnpx -y clerk@latest init --app <application_id>. For an existing application, have them sign in, link the selected application if needed, then runnpx -y clerk@latest env pull. Alternatively, ask the developer for the publishable key from Clerk Dashboard → API keys and wait. Don't continue until a real key is set, and never run a freshinitfor an existing Clerk project.Native API dashboard toggle — Clerk's Native API must be enabled for the instance: Clerk Dashboard → Native applications (
https://dashboard.clerk.com/~/native-applications). Tell the developer to verify this during setup; it is required for any native integration.Factor availability — Before implementing a specific strategy (SMS, email code, social provider), confirm it's enabled for the instance. Derive the Frontend API URL from the publishable key (base64-decode the middle segment) and fetch
<frontendApiUrl>/v1/environment?_is_native=true, or ask the developer to check the dashboard (User & authentication). SMS in particular is instance-configuration-dependent — code written for a disabled factor fails at runtime, not build time.Current custom-flows API only —
useSignIn()/useSignUp()from@clerk/expo(v3.4+) return{ signIn, errors, fetchStatus }and use method-based flows:signIn.password(),signIn.phoneCode.sendCode(),signIn.finalize(). Never generate the legacy pattern: destructuringisLoaded/setActivefromuseSignIn()/useSignUp()(the current hooks don't return them), orsignIn.create()chained withprepareFirstFactor()/attemptFirstFactor()+setActive({ session }). That pattern lives at@clerk/expo/legacyand is only for maintaining existing legacy code, never for new work. Scope notes:isLoadedfromuseAuth()/useUser()is current API and required in guards;signIn.create()itself still exists for advanced cases — prefer the factor-specific methods.useSSO(), neveruseOAuth()—useOAuthis deprecated. Note the asymmetry:startSSOFlow()still returns{ createdSessionId, setActive }and requiressetActive({ session: createdSessionId })— SSO does not usefinalize().Token cache —
tokenCachefrom@clerk/expo/token-cacheonClerkProvider. Never useexpo-secure-storedirectly for session tokens, never AsyncStorage.resourceCache, neversecureStore— if offline resource caching comes up,@clerk/expo/secure-storeis deprecated; useresourceCachefrom@clerk/expo/resource-cache.Build-type gating — Native components (
@clerk/expo/native) and native hooks (useSignInWithGoogle,useSignInWithApple,useLocalCredentials) require a development build (npx expo run:ios/run:android), not Expo Go, and don't exist on web. For web targets use@clerk/expo/webcomponents or custom flows. State the build requirement before implementing a native-only feature.Combined sign-in-or-up default — one combined flow unless the developer asks for separate sign-in and sign-up screens.
Bot protection — custom sign-up screens must render
<View nativeID="clerk-captcha" />; Clerk's bot protection is on by default and needs this mount point.Source verification for volatile surfaces — before using native component props or native hook options, confirm against the installed package:
node_modules/@clerk/expo/dist/native/*.d.tsandpackage.jsonexports. The installed version wins over this skill if they disagree.Freshness gate — this skill was verified against
@clerk/expo3.6.x. Check the installed version (node_modules/@clerk/expo/package.json). If it is a newer minor or major, treat this skill's code snippets as suspect: re-verify against the docs URL cited next to each snippet (every reference section carries one) or the installed.d.tsbefore using them. If it is older than 3.4, the method-based custom-flows API may not exist — offer an upgrade instead of writing legacy code.
Version Notes (v3.5–v3.6, June 2026)
Minimum React Native raised to 0.75 in v3.5.0 (iOS SDK now links via SPM podspec). Peer range:
expo >=53 <57.Native components matured: iOS moved to Expo Modules; native↔JS session sync is automatic and bidirectional — never call
setActive()after native-component auth.The config plugin accepts a
themeJSON file for native component styling (see references/prebuilt-components.md).Native Google sign-in will move to a separate
@clerk/expo-google-signinpackage in the next major (the@clerk/expo/googleimport keeps working in v3; a dev warning announces the migration). Don't preinstall the new package on v3.
Common Pitfalls
| Level | Issue | Prevention |
|---|---|---|
| CRITICAL | Generating legacy custom-flow code (signIn.create + prepareFirstFactor + setActive) | Use the current method-based API (Gate 4) |
| CRITICAL | Using useOAuth() | Use useSSO() (Gate 5) |
| CRITICAL | Implementing SMS/social auth without checking the factor is enabled | Check environment/dashboard first (Gate 3) |
| CRITICAL | Native components targeted at Expo Go or web | Require a dev build; offer custom flows otherwise (Gate 8) |
| CRITICAL | Sign-up screen missing <View nativeID="clerk-captcha" /> | Always include it (Gate 10) |
| HIGH | NEXT_PUBLIC_ env prefix, or env var read inside node_modules | EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, passed explicitly to ClerkProvider |
| HIGH | Session lost on restart | tokenCache from @clerk/expo/token-cache on the provider |
| HIGH | Calling setActive() after AuthView / UserButton auth | Native components sync sessions automatically |
| HIGH | Pairing AuthView with useSignInWithGoogle/useSignInWithApple | AuthView renders enabled social providers itself |
| HIGH | Calling WebBrowser.maybeCompleteAuthSession() manually | ClerkProvider handles it |
| HIGH | Splitting sign-in / sign-up without being asked | Combined flow by default (Gate 9) |
| MEDIUM | Missing isLoaded check before isSignedIn in guards | Always gate on isLoaded first |
| MEDIUM | Using yalc/pnpm link for local @clerk/expo development | Use Verdaccio or pkg.pr.new |
See Also
clerk— top-level routerclerk-swift/clerk-android— native mobile SDKsclerk-orgs,clerk-billing,clerk-webhooks— feature skills (hooks work the same in Expo)Installed package source:
node_modules/@clerk/expo/Clerk Expo quickstart examples — three official example apps: JS-only (Expo Go), JS + native sign-in buttons, native components
Files
- ATTRIBUTION.mdAdded by AutoGineer824 B
- SKILL.md12.1 KiB
- evals/evals.json6.6 KiB
- references/custom-flows.md9.7 KiB
- references/prebuilt-components.md5.2 KiB
- references/protected-routes.md2.5 KiB
- references/recipes.md5.1 KiB
- references/setup.md5.6 KiB
- references/sso-and-native-auth.md6.2 KiB
references/custom-flows.md
Custom flows (@clerk/expo hooks)
Build your own auth UI with useSignIn() / useSignUp(). Works everywhere including Expo Go and web. Use when the developer wants their own UI, needs Expo Go, or asked for a specific strategy like SMS.
Canonical docs — use the Expo example when the guide has one; otherwise adapt the guide to Expo. Fetch the relevant guide to re-verify if the installed SDK is newer than 3.6.x:
Email links — Expo web custom flows only
The current API (v3.4+) — not the legacy one
useSignIn() and useSignUp() return { signIn, errors, fetchStatus } / { signUp, errors, fetchStatus }:
Method-based flows:
signIn.password({...}),signIn.phoneCode.sendCode({...}),signUp.verifications.verifyEmailCode({...}). Every method resolves to{ error: ClerkError | null }— checkerror, don't rely on try/catch for API errors.errors— reactive error state; field-level errors aterrors.fields.<name>(e.g.errors.fields.identifier,errors.fields.code).fetchStatus—'idle' | 'fetching'; use it to disable submit buttons.signIn.status—'needs_identifier' | 'needs_first_factor' | 'needs_second_factor' | 'needs_client_trust' | 'needs_new_password' | 'complete'.signUp.status—'missing_requirements' | 'complete', withsignUp.unverifiedFields/signUp.missingFieldssaying what's left.finalize({ navigate })— converts acompletesign-in/up into the active session. ReplacessetActive({ session }).reset()— clears the attempt so the user can start over (local-only, no API call).
Never generate the legacy shape for new code: isLoaded/setActive destructured from useSignIn()/useSignUp() (the current hooks don't return them — isLoaded from useAuth()/useUser() is fine), or signIn.create() chained with prepareFirstFactor()/attemptFirstFactor() + setActive({ session: createdSessionId }). That API lives at @clerk/expo/legacy and is only for maintaining code that already uses it. (signIn.create() still exists on the new resource but is for advanced cases — prefer the factor-specific methods.)
If the installed @clerk/expo is older than 3.4 and hooks don't have this shape, tell the developer and offer to upgrade rather than writing legacy code.
Before writing flow code
Check enabled factors (SKILL.md Gate 3). Derive the Frontend API URL from the publishable key and fetch
<frontendApiUrl>/v1/environment?_is_native=true, or have the developer confirm in the dashboard. Only implement enabled strategies; if the user asked for a disabled one (common with SMS), tell them to enable it in Clerk Dashboard → User & authentication first.Combined flow by default — one screen that signs in or signs up, unless separation is requested.
Captcha mount point — every screen that can create a sign-up must render
<View nativeID="clerk-captcha" />.
Shared finalize helper
All flows end the same way. Session tasks (e.g. forced MFA enrollment, org selection) must be handled before navigating:
const navigateAfterAuth = ({ session, decorateUrl }) => {
if (session?.currentTask) {
// Route to your session-task UI instead of home.
// https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
return
}
const url = decorateUrl('/')
if (url.startsWith('http')) window.location.href = url // Expo web
else router.push(url as Href)
}
// on status === 'complete':
await signIn.finalize({ navigate: navigateAfterAuth }) // same shape for signUp.finalizeEmail + password (sign-in)
import { useSignIn } from '@clerk/expo'
const { signIn, errors, fetchStatus } = useSignIn()
const handleSubmit = async () => {
const { error } = await signIn.password({ emailAddress, password })
if (error) return // surface errors.fields.identifier / errors.fields.password in the UI
if (signIn.status === 'complete') {
await signIn.finalize({ navigate: navigateAfterAuth })
} else if (signIn.status === 'needs_second_factor') {
// MFA step — see below
} else if (signIn.status === 'needs_client_trust') {
// New-device verification: send a code, then verify with signIn.mfa.verifyEmailCode
const emailFactor = signIn.supportedSecondFactors?.find((f) => f.strategy === 'email_code')
if (emailFactor) await signIn.mfa.sendEmailCode()
}
}Sign-up mirror: signUp.password({ emailAddress, password }), then signUp.verifications.sendEmailCode(), collect the code, signUp.verifications.verifyEmailCode({ code }), then signUp.finalize(...). Show the verify step when signUp.status === 'missing_requirements' && signUp.unverifiedFields.includes('email_address').
Phone / SMS OTP
Requires Phone number + SMS verification code enabled in the dashboard (SMS is billable and instance-dependent — check first, Gate 3).
Sign-up:
const { signUp, errors, fetchStatus } = useSignUp()
const handleSubmit = async () => {
const { error } = await signUp.create({ phoneNumber }) // E.164, e.g. +15551234567
if (!error) await signUp.verifications.sendPhoneCode()
}
const handleVerify = async () => {
await signUp.verifications.verifyPhoneCode({ code })
if (signUp.status === 'complete') await signUp.finalize({ navigate: navigateAfterAuth })
}
// Show the code input when:
// signUp.status === 'missing_requirements' && signUp.unverifiedFields.includes('phone_number') && signUp.missingFields.length === 0
// Resend: signUp.verifications.sendPhoneCode()Sign-in:
const { signIn, errors, fetchStatus } = useSignIn()
const handleSubmit = async () => {
// Creates the sign-in attempt AND sends the SMS in one call — no signIn.create() needed
const { error } = await signIn.phoneCode.sendCode({ phoneNumber })
if (error) return // surface errors.fields
}
const handleVerify = async () => {
await signIn.phoneCode.verifyCode({ code })
if (signIn.status === 'complete') await signIn.finalize({ navigate: navigateAfterAuth })
}
// Resend: signIn.phoneCode.sendCode() with no args — the sign-in already existsEmail OTP is the same shape: emailCode.sendCode({ emailAddress }) (also self-creating) / emailCode.verifyCode({ code }) on sign-in, sendEmailCode() / verifyEmailCode() on sign-up verifications.
Combined sign-in-or-up
Attempt sign-in; on form_identifier_not_found, switch to sign-up with the same credentials:
const { error } = await signIn.password({ emailAddress, password })
if (error) {
if (error.errors[0].code === 'form_identifier_not_found') {
const { error: signUpError } = await signUp.password({ emailAddress, password })
if (signUpError) return
await signUp.verifications.sendEmailCode()
if (signUp.unverifiedFields?.includes('email_address')) setShowCodeStep(true)
return
}
return // real error — surface errors.fields
}
// continue sign-in path (complete / needs_second_factor / needs_client_trust)For the OTP-only variant, do the same with signIn.phoneCode / signUp.create({ phoneNumber }).
MFA / 2FA (second factor)
When signIn.status === 'needs_second_factor', check signIn.supportedSecondFactors and use signIn.mfa:
| Factor | Send | Verify |
|---|---|---|
| SMS code | signIn.mfa.sendPhoneCode() | signIn.mfa.verifyPhoneCode({ code }) |
| Email code | signIn.mfa.sendEmailCode() | signIn.mfa.verifyEmailCode({ code }) |
| TOTP (authenticator app) | — | signIn.mfa.verifyTOTP({ code }) |
| Backup code | — | signIn.mfa.verifyBackupCode({ code }) |
After a successful verify, signIn.status becomes complete → finalize(). The same mfa methods serve needs_client_trust (new-device verification).
Other flows
Forgot password:
signIn.resetPasswordEmailCode.sendCode()→verifyCode({ code })(status becomesneeds_new_password) →submitPassword({ password }). Phone variant:signIn.resetPasswordPhoneCode.*.Email link (Expo web only):
signIn.emailLink.sendLink({ ... })+signIn.emailLink.waitForVerification(). Per Clerk's sign-in options, native iOS and Android don't support JavaScript-only custom email-link flows. For native custom UI, use email codes; for native email links, use<AuthView />in a development build.Passkeys:
signIn.passkey(); requires@clerk/expo-passkeysand dev build.Social/SSO: see sso-and-native-auth.md — SSO does not use this method API.
Verification checklist
No legacy API in generated code (no
prepareFirstFactor, nosetActive({ session })outside SSO).Every implemented strategy confirmed enabled for the instance.
Errors surfaced from
errors.fields.*; buttons disabled whilefetchStatus === 'fetching'.finalize({ navigate })handlessession.currentTaskbefore navigating.Sign-up screens include
<View nativeID="clerk-captcha" />.Verify step gated on
status/unverifiedFields, with a resend button and areset()escape hatch.One real end-to-end auth exercised; session survives restart.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Needs an API key, Needs Node.js
The author says: Requires @clerk/expo v3.4+ (written against v3.6.x, July 2026). Expo SDK 53-56, React Native 0.75+.
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- clerk
- Publisher
- Clerk (Official)
- Source
- clerk/skills · skills/clerk-expo · commit c5d2c0d
- Licence
- MIT License (redistribution allowed)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by clerk or Clerk.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download clerk-expo.zip