Skip to main content
A bundled flow is a flow you ship inside your app bundle as a build-time default. It is the last automatic tier in the resolution chain: when a live resolve fails and there is no usable cache, the SDK renders the bundled flow, so onboarding still works on a device that has never been online. Bundled flows are a safety net, not the source of truth. A live or cached flow always wins. The bundled copy only renders when everything above it is unavailable.

Where bundled flows sit in the fallback chain

The bundled flow is Tier 3: tried only after the fresh cache (Tier 0), the live network resolve (Tier 1), and the stale cache (Tier 2) all come up empty. If even the bundled flow is missing, the SDK falls through to your own native fallback. See Error handling for the fallback APIs.

Configure bundled flows

The simplest path is to declare bundled flows in FlowPilotConfiguration.
bundledFlows
[String: String]
default:"[:]"
A map of placement key to the base name of a JSON resource in your app’s main bundle. For example ["onboarding": "OnboardingDefault"] loads OnboardingDefault.json.
bundledFlowAssets
[String: BundledFlowAssets]
default:"[:]"
A map of placement key to the offline image and font assets for that flow, so it renders with no remote requests at all. Optional: a bundled flow with no assets still renders, it just fetches images and fonts from the network (and falls back to blank images / system fonts when offline).
import FlowPilotSDK

FlowPilot.configure(
    FlowPilotConfiguration(
        apiKey: "fp_live_xxxxxxxxxxxxxxxx",
        appId: "your-app-id",
        bundledFlows: [
            "onboarding": "OnboardingDefault"  // loads OnboardingDefault.json from the main bundle
        ]
    )
)
The bundled JSON can be either a full resolve-response payload (preferred, it carries the media base URL, icon base URL, and font manifest) or a bare flow definition. A bare definition still renders; it is wrapped with synthetic identifiers internally.

Bundling the assets for full offline

A flow’s images and custom fonts normally point at remote URLs. Offline, those requests fail and the flow renders with blank images and system fonts. To render the flow fully offline, ship its assets too and declare them with BundledFlowAssets. BundledFlowAssets has two initializers:
// Assets inside the app bundle, optionally narrowed to a folder reference.
public init(bundle: Bundle = .main, subdirectory: String? = nil, manifest: String = "manifest")

// Assets in a plain filesystem directory (mainly for tests).
public init(directoryURL: URL, manifest: String = "manifest")
The recommended layout is a .flowassets folder dropped into your app target as a folder reference:
OnboardingDefault.flowassets/
  flow.json        # the resolve response
  manifest.json    # maps each remote URL to a local file
  images/<file>.png
  icons/<file>.svg
  fonts/<family-weight>.ttf
FlowPilot.configure(
    FlowPilotConfiguration(
        apiKey: "fp_live_xxxxxxxxxxxxxxxx",
        appId: "your-app-id",
        bundledFlows: [
            "onboarding": "OnboardingDefault"   // OnboardingDefault.flowassets/flow.json
        ],
        bundledFlowAssets: [
            "onboarding": BundledFlowAssets(subdirectory: "OnboardingDefault.flowassets")
        ]
    )
)

Register at runtime

If your flow JSON is not a static main-bundle resource (for example it arrives as in-memory Data, or lives in a custom bundle), register it after configuring. All of these are verified public signatures on FlowPilot:
// 1. From raw JSON data, optionally with offline assets.
public func registerBundledFlow(placementKey: String, json: Data, assets: BundledFlowAssets? = nil)

// 2. From a bundle resource (default extension "json", default bundle .main).
public func registerBundledFlow(
    placementKey: String,
    resource: String,
    withExtension ext: String = "json",
    in bundle: Bundle = .main,
    assets: BundledFlowAssets? = nil
)

// 3. From an exported .flowassets folder reference (the all-in-one offline default).
public func registerBundledFlow(
    placementKey: String,
    assetBundle subdirectory: String,
    in bundle: Bundle = .main,
    flowResource: String = "flow",
    manifestResource: String = "manifest"
)

// 4. Register only the offline assets for a flow whose JSON was registered elsewhere.
public func registerBundledFlowAssets(placementKey: String, assets: BundledFlowAssets)
The third overload is the easiest for a .flowassets bundle: it wires up flow.json, manifest.json, and the asset folders in one call.
// Register the whole offline default in one line.
FlowPilot.shared?.registerBundledFlow(
    placementKey: "onboarding",
    assetBundle: "OnboardingDefault.flowassets"
)

Export a flow for bundling

You need the flow’s JSON (and, for full offline, its assets) on disk before you can bundle it. There are two ways.

Option A: Export JSON from the editor

In the Flow Editor, open the Design panel, switch to the Flow Settings tab, and find the Export & Import section. Click Export JSON to view the flow, then Copy or Download it. The download is named after the flow (for example my-onboarding.json). This produces the flow definition only. Drop the file into your app target and register it with bundledFlows or registerBundledFlow(resource:). It does not bundle images or fonts, so an offline render uses blank images and system fonts unless you also supply assets.

Option B: Export a full .flowassets bundle with the CLI

The SDK package ships a command-line tool, flowpilot-export, that snapshots the currently live flow at a placement and downloads its images, icons, and fonts into a self-contained .flowassets folder. Run it on a dev machine or in CI:
swift run flowpilot-export \
  --api-key fp_live_xxxxxxxxxxxxxxxx \
  --app-id your-app-id \
  --placement onboarding \
  --env production \
  --out Sources/Resources/OnboardingDefault.flowassets
It writes flow.json, manifest.json, and the images/, icons/, and fonts/ folders. Add the resulting folder to your app target as a folder reference, then register it with the assetBundle: overload (or bundledFlowAssets) as shown above. Per-asset download failures are non-fatal: a partial export still produces a usable bundle and reports what it skipped.
There is no “Export offline bundle” button inside the dashboard today. The editor’s Export JSON gives you the flow definition; the flowpilot-export CLI is the supported way to produce a full .flowassets bundle with assets.

Example

Ship OnboardingDefault.flowassets for the onboarding placement, configured two ways. Declarative, in the configuration:
FlowPilot.configure(
    FlowPilotConfiguration(
        apiKey: "fp_live_xxxxxxxxxxxxxxxx",
        appId: "your-app-id",
        bundledFlows: ["onboarding": "OnboardingDefault"],
        bundledFlowAssets: [
            "onboarding": BundledFlowAssets(subdirectory: "OnboardingDefault.flowassets")
        ]
    )
)
Or imperatively, after configuring (equivalent for a .flowassets folder):
FlowPilot.shared?.registerBundledFlow(
    placementKey: "onboarding",
    assetBundle: "OnboardingDefault.flowassets"
)
Either way, when a present for onboarding cannot reach the network and has no cache, the bundled flow renders from local bytes.

Notes

  • Bundled flows go stale. They are a snapshot from export time. The live version of the flow can change after you ship. Treat the bundled copy as a fallback, not the latest experience. Re-export when the flow changes meaningfully.
  • Keep them small. Bundled assets add to your app’s download size. Bundle the one or two placements a user must see offline (usually onboarding), not your whole catalog.
  • Bundled renders are tagged. When a bundled flow is served, every automatic event carries delivery_source = bundled_default, so you can measure how often the offline path is hit. See Analytics integration.

Common mistakes

  • Resource name or extension mismatch. bundledFlows: ["onboarding": "OnboardingDefault"] expects OnboardingDefault.json in the main bundle. A typo or a .txt extension means the resource is not found and the tier is skipped. Check the SDK logs for a “resource not found” warning.
  • Forgetting the assets. A bundled flow with no bundledFlowAssets renders offline, but its remote images 404 and custom fonts fall back to system fonts. For a polished offline render, ship the .flowassets bundle.
  • Assuming the bundled flow overrides the live flow. It does not. The bundled flow is the last resort. As long as the network or cache can produce a flow, that one renders. Use it as insurance, not as a way to pin a version.
  • Adding the folder as a group, not a folder reference. A .flowassets folder must be added as a folder reference (blue folder) so its subdirectory structure survives into the bundle. Added as a group, the files flatten and the manifest paths break.

Troubleshooting

SymptomLikely causeFix
Bundled flow never rendersJSON resource not found, or not actually offlineVerify the resource name and extension; force the offline path with airplane mode and a cleared cache
Images blank only when offlineNo bundledFlowAssets for the placementExport a .flowassets bundle and register it
Fonts look wrong offlineFont files missing from the manifest or bundleRe-export with the CLI so manifest.json and fonts/ are complete
flowpilot-export reports failuresSome asset URLs were unreachable at export timeRe-run when online; a partial export still works but is missing those assets