Overview

A full-featured Expo and React Native starter with Supabase, RevenueCat, app services, and EAS distribution wired together.

The Expo (React Native) template is a full-featured mobile starter for teams that want one TypeScript codebase across iOS, Android, and web. It ships with Expo Router navigation, a Supabase backend, RevenueCat purchase flows, account-bound entitlements, analytics, crash reporting, launch gates, push inbox plumbing, deep links, and a demo surface you can remove once your own product screens take over.

The app plumbing above is real and integrated. Some surfaces are intentionally Example or Removable demo features, and you should replace them with your product workflow before launch.

The Expo template is the original cross-platform mobile starter. The Swift and Kotlin templates were ported from it, but their native stacks have diverged, so these docs describe the TypeScript source in ../soar-expo.

Configuration model

The app reads public client configuration from EXPO_PUBLIC_* variables and build-time metadata from app.config.ts. The Supabase URL and anon key are the first values to set because lib/supabase.ts constructs the client at module load time and throws if either is missing. Other providers check their own keys and no-op, hide actions, or show "not configured" feedback when absent.

EXPO_PUBLIC_* values are bundled into the app. Use them only for publishable client keys such as Supabase anon keys, RevenueCat public SDK keys, PostHog project keys, and Sentry DSNs. Server secrets belong in Supabase Edge secrets or EAS secrets.

Tech stack

LayerChoice
RuntimeReact Native 0.81.5, React 19.1, Expo SDK 54
App shellExpo Router 6 with typed routes, route groups, modal screens, and Metro static web output
Native postureNew Architecture enabled, Android edge-to-edge, portrait orientation
StylingNativeWind 4, Tailwind CSS, React Native Reusables, @rn-primitives/*, CVA
BackendSupabase JS 2 typed with types/database.ts
Data cacheTanStack Query 5 with AsyncStorage persistence and NetInfo online state
Formsreact-hook-form + zod schemas
Session storageexpo-secure-store through ChunkedSecureStore on native; localStorage on web
PurchasesRevenueCat via react-native-purchases; app entitlement reads Supabase subscriptions
AnalyticsPostHog React Native with typed event helpers, identify/reset, feature flags
Crash reportingSentry React Native Expo plugin, navigation integration, Expo update tags, sourcemaps
Localizationi18n-js + expo-localization, English and Simplified Chinese
Notificationsexpo-notifications permission flow, local inbox, Expo push-token retrieval
Device servicesexpo-local-authentication, expo-haptics, expo-store-review, tracking transparency
Mediaexpo-image, expo-image-picker, expo-document-picker, expo-image-manipulator
Launch and updatesLottie splash asset, app-version gate, expo-updates, EAS Build/Submit

Feature status

Status labels used throughout this root:

  • Integrated — wired in the app; activates when its key or service is configured.
  • Example — a working reference feature meant to be replaced.
  • Removable demo — useful for evaluating the template, but safe to delete.
  • Native-only — needs a native iOS/Android build and no-ops or degrades on web.
  • Device-only — needs a physical device for the full behavior.
FeatureStatusPlatform supportNotes
Email authIntegratediOS / Android / Web / Expo GoSupabase email/password, sign-up OTP, login OTP, forgot/reset, change email/password
Apple Sign-InNative-onlyiOS native buildsexpo-apple-authentication; unavailable on Android and web
Google Sign-InNative-onlyiOS / Android native buildsOptional native module; hidden by runtime failure on web and requires Google OAuth IDs
ProfileExampleiOS / Android / Web / Expo GoProfile row, avatar upload, signed URLs, account settings
TodosExampleiOS / Android / Web / Expo GoSupabase CRUD with TanStack Query pagination and optimistic cache updates
Upload sampleExampleiOS / Android / Web variesDocument picker to sample-uploads/<user-id>/... plus sample_uploads table row
SubscriptionsIntegratediOS / Android native buildsRevenueCat purchase/restore; entitlement source of truth is Supabase
Redeem codeNative-onlyiOS / Android native buildsiOS redemption sheet; Android opens Google Play redeem URL; web shows guidance
Push inbox and permissionsDevice-onlyNative device bestLocal inbox, permissions, Android channels, Expo push token; server sending is not wired by default
Deep linksIntegratediOS / Android / WebCustom Expo URL plus HTTPS links when LINKING_DOMAIN is set
AnalyticsIntegratediOS / Android / Web / Expo GoPostHog screen tracking, typed events, identify/reset, feature flags
Crash reportingIntegratediOS / Android / Web / Expo GoSentry enabled when DSN exists; native frame tracking disabled in Expo Go
App gatesIntegratediOS / Android / WebOnboarding, legal consent, version gate, auth auto-dismiss
Biometric lockNative-onlyiOS / Android deviceUses expo-local-authentication; requires enrolled biometrics
Permissions primerIntegratediOS / Android / Web fallbacksNotifications, photos, camera, ATT; web tracking fallback returns granted
Store reviewNative-onlyiOS / Android store-capable buildsWeb fallback is a no-op
Offline banner and cacheIntegratediOS / Android / WebNetInfo drives Query online state; query cache persists with AsyncStorage
Toast and hapticsIntegratediOS / Android / Web fallbackToast provider is global; haptics are native where available
i18nIntegratediOS / Android / Web / Expo GoEnglish and Simplified Chinese with runtime language state
ThemingIntegratediOS / Android / Web / Expo GoNativeWind color scheme, persisted preference, iOS-style color tokens
Refer a friendExampleiOS / Android / Web variesShare sheet using generated app links
Component galleryExampleiOS / Android / Web / Expo GoReact Native Reusables component catalog
ShowcaseRemovable demoiOS / Android / Web / Expo GoCapability catalog and demo links for evaluating the starter
Animated splashIntegratediOS / Android / WebSplash assets configured in Expo config; initial app surfaces match grouped background

App surface map

The app is composed from the root outward:

  • Root providersapp/_layout.tsx mounts QueryProvider, AuthProvider, AnalyticsProvider, ConsentProvider, OnboardingProvider, EntitlementProvider, PurchasesProvider, I18nProvider, ThemePreferenceProvider, NotificationsProvider, BiometricProvider, ToastProvider, bottom sheets, portals, and Sentry navigation tracking.
  • Gate stackOnboardingGate, AuthGate, VersionGate, ConsentGate, and BiometricGate run around the Expo Router Stack. Auth is guest-first: most screens are browsable signed out, and signed-in-only actions use SignInGate.
  • Auth stackapp/(auth) contains login, sign-up, forgot-password, and reset-password screens.
  • Main shellapp/(tabs) has four tabs: Home, Component, Showcase, and Me. A drawer exposes Home, Component, Showcase, Todos, Upload sample, Refer, Profile, Account Security, Notifications, and Sign In.
  • Pushed screenstodos, profile, account-security, notifications, permissions, refer, upload-sample, legal/*, manage-subscription, and showcase detail routes.
  • Modalspaywall and redeem-code are modal stack screens.

Cross-platform entitlement

Paid access is account-bound. RevenueCat handles purchase, restore, offerings, and local CustomerInfo, but the app gates access from the shared Supabase subscriptions table through useEntitlement().isEntitled. That table is also the read model used by the desktop template's Creem webhook, so entitlement can follow the same Supabase user across platforms.

The single rule: RevenueCat is the purchase mechanism, not the source of truth for gating. The server-side row wins.

Start here

On this page