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

# Flow schema reference

> The JSON shape of a published FlowPilot flow (schema version 9), for exporting, bundling, and debugging.

A flow is a JSON object. The editor builds it, the backend stores and serves it, and the iOS SDK renders it. This page documents the exact shape so you can read an exported flow, bundle one offline, or debug a resolve response.

The schema is the single source of truth for both the editor and the SDK. Its current content version is `CURRENT_SCHEMA_VERSION = 9` (see [Schema versions](/reference/schema-versions)).

<Note>
  This is the published and exported shape. The SDK receives it as the `flow_schema` field of a [resolve response](/reference/sdk-rest-api). You normally never write this by hand; the editor generates it. Read it when you export a flow, bundle one for offline use, or inspect what the SDK actually received.
</Note>

## Top level: `FlowDefinition`

The root object.

| Field          | Type                             | Notes                                                                                                  |
| -------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `id`           | `string`                         | Flow ID.                                                                                               |
| `name`         | `string`                         | Display name.                                                                                          |
| `version`      | `number`                         | The flow's content/migration version (1 to 8). Bumped by migrations on load.                           |
| `appId`        | `string` (optional)              | Owning app, for multi-app setups.                                                                      |
| `createdAt`    | `string`                         | ISO 8601 timestamp.                                                                                    |
| `updatedAt`    | `string`                         | ISO 8601 timestamp.                                                                                    |
| `nodes`        | `FlowNode[]`                     | The graph nodes. See [Nodes](#nodes).                                                                  |
| `edges`        | `FlowEdge[]`                     | The graph connections. See [Edges](#edges-flowedge).                                                   |
| `entryNodeId`  | `string`                         | ID of the node where the flow starts.                                                                  |
| `chrome`       | `GlobalChrome` (optional)        | **Deprecated.** Replaced by `persistentUI`. Kept only so old flows migrate.                            |
| `persistentUI` | `PersistentUI` (optional)        | Navigation bar, footer, and overlay zones that wrap every screen. See [Persistent UI](#persistent-ui). |
| `variables`    | `FlowVariable[]` (optional)      | Flow state. See [Variables](#variables).                                                               |
| `globalStyles` | `FlowGlobalStyles` (optional)    | Theme tokens (colors, typography, spacing, radii, shadows).                                            |
| `settings`     | `FlowSettings` (optional)        | Default transitions, localization, analytics provider.                                                 |
| `background`   | `ScreenBackground` (optional)    | Flow-level default background, inherited by screens. See [Backgrounds](/editor/backgrounds).           |
| `metadata`     | `Record<string, any>` (optional) | Editor/analytics extras.                                                                               |

## Nodes

`nodes` is a discriminated union keyed on `kind`. Every node extends a base:

| Field      | Type                             | Notes                                                                        |
| ---------- | -------------------------------- | ---------------------------------------------------------------------------- |
| `id`       | `string`                         | Unique within the flow.                                                      |
| `name`     | `string`                         | Display name.                                                                |
| `kind`     | `FlowNodeKind`                   | One of `screen`, `condition`, `assign`, `api`, `abTest`, `event`, `subflow`. |
| `tags`     | `string[]` (optional)            | Free-form tags.                                                              |
| `metadata` | `Record<string, any>` (optional) | Editor extras.                                                               |

<Warning>
  Only `screen` produces UI. `condition`, `assign`, and `abTest` are logic nodes the SDK evaluates to decide the next node. The kinds `api`, `event`, and `subflow` are declared in the schema but are **not supported by the current SDKs**: they are skipped during rendering. The editor also has no UI to create non-screen nodes today (see [Navigation and branching](/editor/navigation-and-branching)). For A/B testing, use [experiments](/dashboard/experiments), not `abTest` nodes.
</Warning>

### `ScreenNode` (`kind: "screen"`)

The only node kind that renders UI. It holds a component tree.

| Field             | Type                                                                | Notes                                                                             |
| ----------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `screenType`      | `"standard" \| "modal" \| "bottomSheet" \| "fullScreen"` (optional) | Screen presentation style.                                                        |
| `layout`          | `ComponentNode`                                                     | The root component (a stack, the "Root Stack").                                   |
| `props`           | object (optional)                                                   | Screen-level settings. See below.                                                 |
| `enterTransition` | `TransitionConfig` (optional)                                       | Per-screen enter transition override.                                             |
| `exitTransition`  | `TransitionConfig` (optional)                                       | Per-screen exit transition override.                                              |
| `chromeOverrides` | `ScreenChromeOverrides` (optional)                                  | **Deprecated.** Use `screenSettings`.                                             |
| `screenSettings`  | `ScreenSettings` (optional)                                         | Per-screen zone settings. See [Zones](/editor/zones).                             |
| `timeline`        | `ScreenTimelineEvent[]` (optional)                                  | Cross-component sequencing. See [Animations](/editor/animations#screen-timeline). |
| `analytics`       | `{ screenKey?, autoTrackImpressions? }` (optional)                  | Per-screen analytics hints.                                                       |

`ScreenNode.props` fields:

| Prop                          | Type                                       | Notes                                                                               |
| ----------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- |
| `background`                  | `ScreenBackground`                         | Layered background for this screen.                                                 |
| `backgroundInherit`           | `boolean`                                  | When true (or undefined), inherit the flow-level background.                        |
| `backgroundColor`, `gradient` | legacy                                     | Kept for backward compatibility during migration.                                   |
| `padding`                     | `number` or `{ top, right, bottom, left }` | Screen padding.                                                                     |
| `safeArea`                    | `boolean`                                  | Respect device safe area.                                                           |
| `statusBarStyle`              | `"light" \| "dark" \| "auto"`              | Status bar appearance.                                                              |
| `includeInProgress`           | `boolean`                                  | Count this screen in auto progress. Defaults to true.                               |
| `hideChrome`                  | `boolean`                                  | **Deprecated** path; current flows use `screenSettings.hideAllZones`.               |
| `animationSpeed`              | `number`                                   | Global multiplier for all animations on the screen. Default 1.0, range 0.25 to 2.0. |
| `particleEffect`              | `ScreenParticleEffect`                     | Auto-play particle effect. See [Particles](/editor/particles).                      |

### `ConditionNode` (`kind: "condition"`)

If/else branching. Evaluated by the SDK; produces no UI.

| Field       | Type                                     | Notes                                                                                              |
| ----------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `condition` | `FlowExpression` (string)                | A safe-subset expression. See [Conditions and expressions](/reference/conditions-and-expressions). |
| `outputs`   | `{ trueLabel?, falseLabel? }` (optional) | Labels for the true/false edges.                                                                   |

### `AssignNode` (`kind: "assign"`)

Sets variables from expressions. Evaluated by the SDK; produces no UI.

| Field         | Type                                                    | Notes                           |
| ------------- | ------------------------------------------------------- | ------------------------------- |
| `assignments` | `{ variableKey: string; expression: FlowExpression }[]` | Each entry writes one variable. |

### `AbTestNode` (`kind: "abTest"`)

A schema-level split. Most A/B testing is done with [experiments](/dashboard/experiments) instead.

| Field           | Type                                            | Notes                    |
| --------------- | ----------------------------------------------- | ------------------------ |
| `experimentKey` | `string`                                        | Stable ID for analytics. |
| `variants`      | `{ id, label, weight (0-100), targetNodeId }[]` | The branches.            |

### Declared but unsupported nodes

These exist in the type definitions but are **not rendered or evaluated by the current iOS or Expo SDKs**. Do not rely on them.

* `ApiNode` (`kind: "api"`): a backend/external HTTP call (`request`, `responseMapping`, `onError`).
* `EventNode` (`kind: "event"`): an event-driven entry point (`eventType`, `eventKey`).
* `SubflowNode` (`kind: "subflow"`): reuse another flow (`subflowId`, `inputMapping`, `outputMapping`).

## Edges (`FlowEdge`)

Connections between nodes. The editor keeps edges in sync with screen order, so a `goNext` action follows the default edge to the next screen.

| Field        | Type                                                                                              | Notes                                                  |
| ------------ | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `id`         | `string`                                                                                          | Unique within the flow.                                |
| `fromNodeId` | `string`                                                                                          | Source node.                                           |
| `toNodeId`   | `string`                                                                                          | Destination node.                                      |
| `kind`       | `"normal" \| "conditionTrue" \| "conditionFalse" \| "error" \| "event" \| "abVariant"` (optional) | Edge role.                                             |
| `label`      | `string` (optional)                                                                               | Display label.                                         |
| `guard`      | `FlowExpression` (optional)                                                                       | Extra condition for multi-branch nodes.                |
| `transition` | `TransitionConfig` (optional)                                                                     | Per-edge transition (highest priority in the cascade). |
| `priority`   | `number` (optional)                                                                               | Ordering hint.                                         |
| `metadata`   | `Record<string, any>` (optional)                                                                  | Editor extras.                                         |

## Components

Each `ScreenNode.layout` is a `ComponentNode` tree:

| Field          | Type                                        | Notes                                                                              |
| -------------- | ------------------------------------------- | ---------------------------------------------------------------------------------- |
| `id`           | `string`                                    | Unique within the flow.                                                            |
| `type`         | `ComponentType`                             | One of the 15 kinds. See [Component reference](/reference/component-reference).    |
| `props`        | `Record<string, any>` (optional)            | Untyped prop bag. Resolved per renderer.                                           |
| `children`     | `ComponentNode[]` (optional)                | For stacks and other containers.                                                   |
| `bindings`     | `{ [propName]: FlowExpression }` (optional) | Bind props to expressions.                                                         |
| `interactions` | `ComponentInteraction[]` (optional)         | Tap/change/appear handlers. See [Actions reference](/reference/actions-reference). |
| `metadata`     | `Record<string, any>` (optional)            | Editor extras.                                                                     |

Props are stored as an untyped `Record<string, any>`. The intended shape per component kind is documented in the [Component reference](/reference/component-reference).

## Persistent UI

`persistentUI` holds three optional zones that wrap every screen. Full coverage is in [Persistent UI zones](/editor/zones). The shape:

| Field                              | Type                        | Notes                                                      |
| ---------------------------------- | --------------------------- | ---------------------------------------------------------- |
| `navigationBar`                    | `PersistentZone` (optional) | Pinned to the top.                                         |
| `footer`                           | `PersistentZone` (optional) | Pinned to the bottom.                                      |
| `overlay`                          | `OverlayZone` (optional)    | Floating layer over content.                               |
| `settings.persistDuringTransition` | `boolean` (optional)        | Whether zones stay fixed during transitions. Default true. |

## Variables

`variables` is an array of `FlowVariable`. Full coverage is in [Variables](/editor/variables). The shape:

| Field          | Type                                                   | Notes                                           |
| -------------- | ------------------------------------------------------ | ----------------------------------------------- |
| `key`          | `string`                                               | Stable key referenced everywhere.               |
| `label`        | `string` (optional)                                    | Display name.                                   |
| `type`         | `"string" \| "number" \| "boolean" \| "list"`          | Data type.                                      |
| `listItemType` | `"string" \| "number" \| "boolean"` (optional)         | Required when `type` is `list`.                 |
| `scope`        | `"global"`                                             | v1 supports global only.                        |
| `lifecycle`    | `"static" \| "session"`                                | Persistence.                                    |
| `source`       | `{ kind: "constant"; value } \| { kind: "sdk"; path }` | Where the value comes from.                     |
| `writable`     | `boolean`                                              | Whether actions can change it.                  |
| `defaultValue` | `VariableValue` (optional)                             | Fallback when the source is unavailable.        |
| `min`          | `number` (optional)                                    | Inclusive lower bound. `number` variables only. |
| `max`          | `number` (optional)                                    | Inclusive upper bound. `number` variables only. |

### Bounds (`min` / `max`)

`min` and `max` declare the range a number variable is allowed to hold. They are
a **guarantee, not a hint**: the SDK clamps every write to the range, so the
value can never leave it — whichever path does the writing.

```json theme={null}
{
  "key": "age",
  "label": "Age",
  "type": "number",
  "scope": "global",
  "lifecycle": "session",
  "source": { "kind": "constant", "value": 25 },
  "writable": true,
  "min": 0,
  "max": 100
}
```

With the above, `age` stays within 0–100 for all of:

* `setVariable` number operations (`increment`, `decrement`, `multiply`, `divide`)
* `assign` expressions
* two-way component bindings (`slider`, `picker`, `ruler`, `input`)
* values written by the host app, and progress restored from a previous session

Either bound may be omitted for no limit on that side. An inverted range
(`min` greater than `max`) is invalid and is ignored rather than pinning the
value. Bounds apply only to `number` variables — for text length use an
`input`'s `minLength` / `maxLength` validation instead.

<Note>
  Clamping is silent, with one exception: when a user **types** an out-of-range
  number into a number `input`, the field snaps into range on blur and shows
  "Must be between X and Y". Clamping every keystroke would make values below
  `min` impossible to type.
</Note>

<Warning>
  `min` / `max` are optional additions, so they do not change the schema version —
  but an SDK build predating them ignores the range. The bound only takes effect
  once the host app upgrades to an SDK that supports it.
</Warning>

## Example

A minimal one-screen flow with a single text and a Continue button:

```json theme={null}
{
  "id": "flow_welcome",
  "name": "Welcome",
  "version": 8,
  "createdAt": "2026-01-10T12:00:00Z",
  "updatedAt": "2026-01-10T12:00:00Z",
  "entryNodeId": "screen_1",
  "nodes": [
    {
      "id": "screen_1",
      "name": "Welcome",
      "kind": "screen",
      "layout": {
        "id": "root",
        "type": "stack",
        "props": { "axis": "vertical", "spacing": 16, "justify": "center" },
        "children": [
          { "id": "title", "type": "text", "props": { "text": "Welcome to Acme", "fontSize": 28, "fontWeight": "700" } },
          {
            "id": "cta",
            "type": "button",
            "props": { "text": "Continue", "variant": "primary" },
            "interactions": [
              { "id": "i1", "event": "onPress", "actions": [{ "kind": "closeFlow" }] }
            ]
          }
        ]
      }
    }
  ],
  "edges": [],
  "variables": []
}
```

## Notes

* The SDK decodes leniently. An unknown `ComponentType` decodes to an internal `unknown` case and renders as nothing (or a placeholder in debug builds) rather than failing the whole screen. A single malformed child is skipped, not fatal.
* `chrome` and `chromeOverrides` are deprecated. New flows use `persistentUI` and `screenSettings`.
* The integer `version` here (the content/migration version, up to 9) is not the same as the wire-format `schema_version` the SDK checks. See [Schema versions](/reference/schema-versions).

## Related pages

* [Component reference](/reference/component-reference)
* [Actions reference](/reference/actions-reference)
* [Conditions and expressions](/reference/conditions-and-expressions)
* [Schema versions](/reference/schema-versions)
