Backend Modes
Standalone (Supabase) vs Unified (Soar API): how to choose, how the mode is resolved, and what does not mix.
The template ships with two complete backends behind one set of business
interfaces. Which one a build is assembled against is a build-level choice
made in the environment (.env / EAS build profile) — a single install
authenticates and stores data against exactly one backend, and there is no
user-facing switch.
| Standalone Mode | Unified Mode | |
|---|---|---|
EXPO_PUBLIC_SOAR_BACKEND_MODE | supabase (default) | soarApi |
| Runtimes | iOS, Android, Web | iOS and Android only |
| Auth | Supabase Auth | Better Auth (/api/auth/*, Bearer token) |
| Data | PostgREST + RLS | Soar App API (/api/app/v1/*) |
| Storage | Supabase Storage | Object storage via presigned upload API |
| Push test | Supabase Edge Function | APNs-only v1 endpoint (iOS); Android registers FCM tokens |
| User ID | Supabase UUID | Better Auth text ID (opaque string) |
| Shares users/data with a web app | No | Yes — same host as a conforming Web template |
Standalone Mode is the classic single-template setup: the Expo app talks directly to your Supabase project on all three runtimes. If you only bought the Expo template, this is your mode and nothing about it changed.
Unified Mode points the native app at a conforming Web host's Soar App API instead. Web and mobile then share one Better Auth user, one todos table, one profile, and one subscription row — a purchase on either platform unlocks the other. In a Unified product, Expo covers iOS/Android and the Web host covers the browser.
Expo Web deliberately rejects Unified. Opening a soarApi build in a
browser raises UnifiedWebUnsupportedError — the app never stores a Bearer
token in browser storage and never falls back to Supabase. It can instead
show a link to your Web deployment via EXPO_PUBLIC_UNIFIED_WEB_APP_URL.
Start here
Pick the mode, then follow exactly one setup guide:
Standalone: Supabase Setup
Create the Supabase project: schema, auth callback, Edge Functions, and secrets.
Unified: Soar API Setup
Connect native builds to a conforming host: env keys, host environment, sessions, and verification.
If you are undecided, the platform-level mode guide and Compatibility Matrix compare the two deployments product-by-product.
How the mode is resolved
EXPO_PUBLIC_SOAR_BACKEND_MODE flows through app.config.ts into the build,
and lib/backend/backend-config.ts resolves it once at startup:
soarApi→ Unified Mode on iOS/Android; on Web it raisesUnifiedWebUnsupportedError.supabaseor unset → Standalone Mode.
The mode is intentionally independent of APP_VARIANT — development,
preview, and production builds may each target their own matching deployment.
lib/backend/backend-factory.ts assembles the service bundle (auth, account,
todos, profile, subscriptions, uploads, device tokens, push test) for the
resolved mode; screens and hooks depend on the business interfaces and contain
no backend type checks.
No silent fallback. If Unified Mode is selected but
EXPO_PUBLIC_SOAR_API_BASE_URL is missing or malformed, startup surfaces a
BackendConfigurationError — the app never quietly degrades to Supabase. A
misconfigured backend should be impossible to miss.
The two modes do not mix
Each backend is a separate identity source. A Supabase session cannot call the Soar App API, and a Better Auth session cannot read your Supabase tables — by design there is no bridging, no dual-write, and no fallback from one mode to the other:
- Auth, data, storage, and subscription always come from the same backend preset. You cannot sign in with Supabase and store todos via the Soar API.
- Users created in one mode do not exist in the other. The same email registered in both places is two unrelated accounts.
Switching a production app between modes is a data migration, not a config
flip. Flipping EXPO_PUBLIC_SOAR_BACKEND_MODE on an install base means:
migrating user accounts into the new identity source (users must
re-authenticate — password hashes and OAuth links don't transfer
automatically), moving todos, profiles, and uploaded files, and re-pointing
RevenueCat appUserIDs to the new user IDs. Pick the mode per deployment
before launch; treat a later switch as a planned migration project.
