Skip to main content
Configure FlowPilot once, as early as possible at app launch, before you present any placement. You pass a FlowPilotConfiguration to FlowPilot.configure(_:). After that, you reach the SDK through the FlowPilot.shared singleton.
Before you startYou need:

Configure at launch

Build a FlowPilotConfiguration with at least an apiKey and an appId, then call FlowPilot.configure(_:).
import SwiftUI
import FlowPilotSDK

@main
struct MyApp: App {
    init() {
        FlowPilot.configure(
            FlowPilotConfiguration(
                apiKey: "fp_live_xxxxxxxxxxxxxxxx",
                appId: "your-app-id"
            )
        )
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
FlowPilot.configure(_:) checks that the API key starts with fp_. If it does not, configuration is skipped, an error is logged, and FlowPilot.shared stays nil. Every later present call goes through FlowPilot.shared?, so a skipped configuration means nothing shows (rather than a crash).

Where the credentials come from

  • apiKey is an SDK API key for your app. Create one in the dashboard (it is shown only once). It is not your dashboard login. See API keys.
  • appId is the App ID of the app that owns your placements. See Workspaces and apps.
Do not hardcode a live API key in source you commit. Inject it from your build configuration, an .xcconfig, or a secrets file that is not checked in.

Configuration options

FlowPilotConfiguration has two required parameters and the rest are optional with sensible defaults.
apiKey
String
required
Your workspace SDK API key. Must start with fp_. From API keys.
appId
String
required
The App ID that owns your placements. From Workspaces and apps.
environment
FlowPilotEnvironment
default:".production"
Which FlowPilot backend to talk to. One of .development, .staging, .production, or .custom(url:). Production points at https://api.flowpilot.io/v1. Use .custom(url:) for a self-hosted or test backend.
context
SDKContext? ([String: Any])
default:"nil"
Initial SDK context used for variable resolution and audience targeting (for example ["user.id": "123", "user.is_premium": true]). You can update it later with updateContext(_:). See Variables and context.
cachingEnabled
Bool
default:"true"
Whether resolved flows are cached. Caching makes repeat presents fast and powers offline fallback. See Caching.
cacheDirectory
String?
default:"nil"
Custom directory for the flow cache. nil uses a platform-default location.
resolveTimeout
TimeInterval
default:"4.0"
Hard wall-clock deadline, in seconds, for resolving a placement (including retries and backoff), so a present can never hang on the network. Values are clamped to a minimum of 0.5. When the deadline is hit, the SDK falls back to cache, then a bundled default, then your host fallback.
bundledFlows
[String: String]
default:"[:]"
Build-time offline defaults, keyed by placement key. Each value is the base name of a JSON resource in your app bundle (for example "OnboardingDefault" loads OnboardingDefault.json). See Offline and bundled flows.
bundledFlowAssets
[String: BundledFlowAssets]
default:"[:]"
Offline image and font assets for bundled flows, keyed by placement key, so a bundled default renders with no network at all. See Offline and bundled flows.
mediaPreloadingEnabled
Bool
default:"true"
Whether images and media are preloaded in screen order when a flow initializes, so users do not see loading states while navigating. See Media preloading.
prefetchOnLaunch
[String]
default:"[]"
Placement keys to warm automatically, once, in the background right after configure(_:). Warming runs at utility priority and never blocks startup: it caches each flow’s JSON and fonts, plus images per prefetchMediaStrategy, so a later present hits the cache. No-op when cachingEnabled is false. Warmed entries only survive their freshness TTL, so this has no visible effect against a 0-TTL backend (.development / .custom). See Prefetching.
prefetchMediaStrategy
PrefetchMediaStrategy
default:".firstScreen"
How aggressively launch prefetch warms images: .none (JSON + fonts only), .firstScreen (also first-screen and persistent-zone images, the default), or .allScreens (also every screen’s images). Also bounds the screen window when an explicit prefetch(_:warmMedia: true) call opts into media warming. See Prefetching.
imageMemoryCacheSize
Int
default:"50 * 1024 * 1024 (50 MB)"
Maximum in-memory image cache size, in bytes. See Caching.
imageDiskCacheSize
Int
default:"200 * 1024 * 1024 (200 MB)"
Maximum on-disk image cache size, in bytes. See Caching.
debugMode
Bool?
default:"nil"
Optional override for the debug overlay. nil leaves it off in release builds.
logLevel
FlowPilotLogLevel
default:".error"
How much the SDK logs. One of .none, .error, .warn, .info, .debug, .verbose.

Example

A complete launch configuration with a few context attributes for targeting and variable resolution:
import FlowPilotSDK

FlowPilot.configure(
    FlowPilotConfiguration(
        apiKey: ProcessInfo.processInfo.environment["FLOWPILOT_API_KEY"] ?? "",
        appId: "app_ios_main",
        environment: .production,
        context: [
            "user.id": "user_123",
            "user.is_premium": false,
            "user.plan": "free"
        ],
        resolveTimeout: 4.0,
        bundledFlows: [
            "onboarding": "OnboardingDefault"
        ],
        prefetchOnLaunch: ["onboarding"],   // warm onboarding in the background
        logLevel: .error
    )
)
After login (or any time your context changes), update it instead of reconfiguring:
FlowPilot.shared?.updateContext([
    "user.id": "user_456",
    "user.is_premium": true,
    "user.plan": "pro"
])

Common mistakes

  • Configuring more than once. Configure a single time at launch. Calling configure(_:) again replaces the shared instance and resets in-memory state.
  • Presenting before configuring. If you present before configure(_:) runs, FlowPilot.shared is nil and nothing happens. Configure first, in your App init() or application(_:didFinishLaunchingWithOptions:).
  • Hardcoding the API key in committed source. Inject it from build settings or a secrets file instead.
  • Wrong environment. A .staging or .development key will not resolve against .production, and vice versa. Match the environment to the key.
  • App ID from the wrong app. The appId must be the app that owns the placement you present, or the resolve returns no flow.

Troubleshooting

  • Nothing presents and FlowPilot.shared is nil. The API key did not start with fp_, so configure(_:) was skipped. Check the logs for “Invalid API key format” and pass a valid SDK key. The SDK exposes this as the invalidApiKey error code.
  • An “sdk_not_initialized” path is hit. You presented before configuring. Move configure(_:) earlier in launch. See Error handling.
  • A resolve returns no flow. Usually the wrong appId, the wrong environment for the key, or a placement with no published flow. Walk the quickstart troubleshooting checklist.