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.

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.

On this page