> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getflowpilot.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Offline and bundled flows

> Ship a default flow (and its assets) inside your app bundle so a placement renders even with no network and no prior cache.

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](/ios-sdk/caching): 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

```mermaid theme={null}
flowchart LR
    A[Fresh cache] -->|miss| B[Live resolve]
    B -->|fail / timeout| C[Stale cache]
    C -->|none| D[Bundled default]
    D -->|none| E[Host fallback]
```

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](/ios-sdk/error-handling) for the fallback APIs.

## Configure bundled flows

The simplest path is to declare bundled flows in [`FlowPilotConfiguration`](/ios-sdk/configuration).

<ParamField path="bundledFlows" type="[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`.
</ParamField>

<ParamField path="bundledFlowAssets" type="[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).
</ParamField>

```swift theme={null}
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:

```swift theme={null}
// 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:

```text theme={null}
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
```

```swift theme={null}
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`:

```swift theme={null}
// 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.

```swift theme={null}
// 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:

```bash theme={null}
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.

<Note>
  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.
</Note>

## Example

Ship `OnboardingDefault.flowassets` for the `onboarding` placement, configured two ways.

Declarative, in the configuration:

```swift theme={null}
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):

```swift theme={null}
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](/ios-sdk/analytics).

## 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

| Symptom                             | Likely cause                                     | Fix                                                                                                   |
| ----------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| Bundled flow never renders          | JSON resource not found, or not actually offline | Verify the resource name and extension; force the offline path with airplane mode and a cleared cache |
| Images blank only when offline      | No `bundledFlowAssets` for the placement         | Export a `.flowassets` bundle and register it                                                         |
| Fonts look wrong offline            | Font files missing from the manifest or bundle   | Re-export with the CLI so `manifest.json` and `fonts/` are complete                                   |
| `flowpilot-export` reports failures | Some asset URLs were unreachable at export time  | Re-run when online; a partial export still works but is missing those assets                          |

## Related pages

* [Caching](/ios-sdk/caching)
* [Media preloading](/ios-sdk/media-preloading)
* [Configuration](/ios-sdk/configuration)
* [Error handling](/ios-sdk/error-handling)
* [Publishing](/editor/publishing)
* [Custom fonts](/dashboard/custom-fonts)
