Configuration
Secrets.xcconfig keys, the xcconfig chain, and graceful degradation.
Mode note: This page documents shared product behavior and the existing Standalone provider details. Unified builds keep the same UI through backend-neutral services and use the provider configuration in Unified / Soar API Setup. Configuration errors never fall back silently.
This is the iOS equivalent of an "environment variables" page. An iOS app has no
.env file or server environment — instead, configuration flows through Xcode's
xcconfig build settings into Info.plist, and the app reads them back through
a typed accessor. This page explains that chain and documents every key.
The xcconfig chain
Configuration moves through five stages:
AppConfig.xcconfig (tracked)
Committed to git with safe placeholder values (e.g. your-supabase-publishable-key).
It ends with #include? "Secrets.xcconfig", so your local file overrides these.
Secrets.xcconfig (git-ignored)
Your real values, created with cp Secrets.example.xcconfig Secrets.xcconfig.
Never committed. Any key you define here overrides the placeholder above.
Info.plist $() substitution
The build settings are surfaced into Info.plist entries using $(KEY)
substitution, so the values ship inside the app bundle as Info dictionary keys.
AppSecrets accessor
Core/Config/AppSecrets.swift reads the Info dictionary and exposes typed,
optional properties (supabaseURL: URL?, revenueCatIOSKey: String?, …).
Per-controller isConfigured no-op
Each controller checks its key. When it's absent, the controller sets
isConfigured = false and no-ops instead of crashing.
Placeholders count as unset. AppSecrets treats a value as missing if it's
empty, still contains the $( substitution marker, or begins with your-. That
means you can leave any placeholder in AppConfig.xcconfig untouched and the
owning feature simply stays inactive — the app still builds and boots. This is
the graceful degradation contract.
Key reference
Every key is optional. The tables below are grouped by service; "When absent" describes the behavior with the key unset.
Supabase
Prop
Type
Required for every Supabase-backed flow (auth, todos, uploads). See Supabase Setup.
Backend mode (Unified Platform)
Prop
Type
See Backend Modes for what each mode provides and what switching one implies.
RevenueCat
Prop
Type
See RevenueCat Setup and Subscriptions.
PostHog (analytics)
Prop
Type
Analytics is also consent-gated — it only starts after the user accepts the consent gate. See Observability.
Sentry (crash reporting)
Prop
Type
Update gate
Prop
Type
Bump MIN_APP_VERSION / LATEST_APP_VERSION in Secrets.xcconfig to exercise
the gate locally. See App Gates.
Links & legal
Prop
Type
See Deep Links for the Universal Links setup.
Google Sign-In
Prop
Type
Both keys ship in Secrets.example.xcconfig. See
Authentication for the full Google flow. Apple
Sign-In needs no key — it uses the Sign in with Apple capability instead.
Server-side secrets never live in the app
Keys in Secrets.xcconfig are client values that ship inside the app
bundle — only put publishable/public values here. Privileged secrets (the APNs
signing key, the RevenueCat webhook auth token, the Supabase service-role key)
live in Supabase Edge Function secrets and are never bundled with the app.
See Supabase Setup.
xcconfig gotcha: // is a comment
In xcconfig files, // starts a comment — which collides with the // in a
URL. The template works around this with the $() empty-substitution trick,
splitting the // so it isn't parsed as a comment:
SUPABASE_URL = https:/$()/your-project-ref.supabase.co$() expands to nothing, so the value reads back as https://your-project-ref.supabase.co.
Keep this pattern whenever you set a URL-valued key.
