CURRENT_SCHEMA_VERSION = 9 (see Schema versions).
This is the published and exported shape. The SDK receives it as the
flow_schema field of a resolve response. 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.Top level: FlowDefinition
The root object.
Nodes
nodes is a discriminated union keyed on kind. Every node extends a base:
ScreenNode (kind: "screen")
The only node kind that renders UI. It holds a component tree.
ScreenNode.props fields:
ConditionNode (kind: "condition")
If/else branching. Evaluated by the SDK; produces no UI.
AssignNode (kind: "assign")
Sets variables from expressions. Evaluated by the SDK; produces no UI.
AbTestNode (kind: "abTest")
A schema-level split. Most A/B testing is done with experiments instead.
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.
Components
EachScreenNode.layout is a ComponentNode tree:
Props are stored as an untyped
Record<string, any>. The intended shape per component kind is documented in the Component reference.
Persistent UI
persistentUI holds three optional zones that wrap every screen. Full coverage is in Persistent UI zones. The shape:
Variables
variables is an array of FlowVariable. Full coverage is in Variables. The shape:
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.
age stays within 0–100 for all of:
setVariablenumber operations (increment,decrement,multiply,divide)assignexpressions- two-way component bindings (
slider,picker,ruler,input) - values written by the host app, and progress restored from a previous session
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.
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.Example
A minimal one-screen flow with a single text and a Continue button:Notes
- The SDK decodes leniently. An unknown
ComponentTypedecodes to an internalunknowncase and renders as nothing (or a placeholder in debug builds) rather than failing the whole screen. A single malformed child is skipped, not fatal. chromeandchromeOverridesare deprecated. New flows usepersistentUIandscreenSettings.- The integer
versionhere (the content/migration version, up to 9) is not the same as the wire-formatschema_versionthe SDK checks. See Schema versions.