Skip to main content
Anything beyond <View> and <Text> — camera, microphone, push notifications, biometrics, files — uses an Expo SDK module. The agent picks the module, asks for permissions correctly, and wires it into your screens. This page is what happens under the hood.
Native capabilities

Permissions, the right way

Expo’s permission helpers are wrapped consistently:
The agent always requests at the moment the user takes the action that needs it, never on app launch. iOS and Play Store reviewers reject apps that ask for everything up front, and users decline.

Camera

Module: expo-camera. Use cases: photo capture, barcode scanning, ID upload.
Web preview limitation: camera does not work in the browser preview. Test on Expo Go.

Push notifications

Module: expo-notifications. Wire-up:
  1. Request permission on first interaction with notifications (e.g. “Enable reminders”).
  2. Get the device token (getExpoPushTokenAsync).
  3. Store the token against the user row in Supabase.
  4. Send pushes server-side via the Expo push API — easiest from a Supabase Edge Function.
For local notifications (e.g. “remind me at 8 AM”), schedule with Notifications.scheduleNotificationAsync. No server needed. Push doesn’t work in the web preview or in iOS Simulator without a paid Apple Developer account. Test on a real device via Expo Go (or a custom EAS build for production).

Biometrics

Module: expo-local-authentication. Use for “Unlock with Face ID” / “Confirm with fingerprint.”
Don’t use biometrics for primary auth — the agent will pair it with a Supabase session token and use biometrics only to gate access to a token already on the device.

Secure storage

Module: expo-secure-store. Backed by Keychain on iOS and EncryptedSharedPreferences on Android. Use for session tokens, refresh tokens, anything sensitive.
Use AsyncStorage (also pre-installed) for non-sensitive data — it’s faster and supports larger payloads.

Geolocation

Module: expo-location. Two permission tiers:
  • When in use — fine grained, works while the app is foregrounded. Default ask.
  • Always — required for background tracking. Triggers an OS-level “always allow” prompt that’s notoriously hard to get past on iOS. Avoid unless the feature genuinely needs it.
Configured in app.json:
Open habits://habit/123 in any browser or message and Expo Router lands on app/habit/[id].tsx. Universal links (https://habits.example.com/...) need an apple-app-site-association and assetlinks.json published on your domain — the agent will tell you the exact files.

In-app purchases

iOS and Play Store policy require IAP for digital goods. Web checkout (Stripe) is not allowed for unlocking app features. Two routes:
  • expo-in-app-purchases — direct integration with StoreKit / Play Billing. Free, but you handle receipt validation server-side yourself.
  • RevenueCat (via Connectors) — managed receipt validation, entitlements, analytics. Free up to $10K MTR.
For physical goods or real-world services (Uber-style), Stripe is fine. The store rules apply only to digital content.

What doesn’t work in Expo Go

A small set of features require a custom EAS build:
  • Custom native modules (anything not in the Expo SDK).
  • Some payment SDKs (RevenueCat works in Expo Go via a config plugin; raw react-native-purchases does not).
  • Some push providers other than Expo’s own.
  • App Clips / App Extensions.
The agent will tell you when a request crosses the Expo Go line. From there it’s a one-time eas build --profile development and you install the resulting build instead of Expo Go.