# Flow syntax reference

Every key a flow file can hold. For how to use them, start with Writing flows; this page is for looking things up.

## Check flows in your editor

Put this line at the top of a flow file, and editors with YAML support complete keys and flag mistakes as you type. `squiggle init` adds it to the first flow it writes.

```yaml
# yaml-language-server: $schema=https://squiggle.sh/schemas/flow-1.0.0.schema.json
flow: checkout
```

## Top-level keys

| Key | What it holds |
| --- | --- |
| `flow` | The name you run the flow by. Required, with steps. |
| `steps` | The list of steps. |
| `appId` | The app’s bundle id. Flows called with runFlow inherit it. |
| `env` | Variables, used as ${NAME} in any string. |
| `secrets` | Variables like env, whose values are never written to recordings or run logs. |
| `tags` | Labels for the flow. A flow tagged probe measures your tools rather than a screen, and the report card never runs it to cover one. |
| `prerequisite` | The screen the flow starts on: `route`, `reset` (auto, scroll, back or relaunch), `app`, and an optional `goal`. See [Repeatable flows](https://squiggle.sh/docs/flows/repeatable). |
| `healing` | adaptive (default), strict or lossy-fallback. See [Healing](https://squiggle.sh/docs/flows/healing). |
| `goal` | Checks, in the same form as assert, that must hold after the last step. |
| `config` | Run settings the flow needs: autoScroll, scrollMode, fastSettle, strictAssert, stepDelayFloorMs, transitionSettleMs, and chaos (a bad-network profile, for a flow that tests the error state). See [Run settings](https://squiggle.sh/docs/run/settings). |
| `expect` | For a flow that exists to catch a known bug: { verdict: fail, failure: <text> }. It passes only by failing with that text. |
| `intent · plan · onStuck · runWith` | The form agents write: intent and plan instead of flow and steps. onStuck is escalate or fail. runWith names the command that runs the program, such as squiggle agent branch; squiggle flow refuses one that names another command (E_RUNS_WITH) and squiggle flows lists it with that command instead of running it. |

Any other key stops the flow from loading, as does a file that isn’t valid YAML (`E_FLOW_PARSE`). Quote a value that contains `#`, such as `text: "Order #20"`: unquoted, YAML reads the rest as a comment.

## Options on any step

These go inside the step’s body, for example `- tap: { testID: promo-close, optional: true }`:

| Option | What it does |
| --- | --- |
| `label` | Names the step in the report. |
| `optional` | The step may fail without failing the flow. |
| `when` | Runs the step only if this condition holds, in the same form as assert. |

## Steps

### Act on a control

| Step | Options |
| --- | --- |
| `tap · doubleTap · longPress` | A selector. expectRoute names the screen the tap opens. |
| `type` | into (a selector), value, and submit (a key to press after). Replaces whatever the field held. |
| `swipe` | direction (up, down, left or right) and amount in points, or to: a percentage of the control. Optional selector. duration (50–5000 ms, default about 300) makes the drag slower; hold (1–3000 ms) keeps the finger still first, a long press that becomes a drag, such as reordering a list. |
| `scroll` | direction (where the content goes) and amount. Optional selector. |
| `scrollUntilVisible` | element (a selector), direction, maxScrolls (default 15), mode (gesture or direct). |

Selector fields are listed on [Selectors](https://squiggle.sh/docs/flows/selectors).

### Wait and check

| Step | Options |
| --- | --- |
| `waitFor` | At least one of route, commitQuiet (React stopped rendering), animationIdle, networkIdle, appRunIdle (a run started elsewhere finished), plus timeout in ms, or auto to use past runs. An app whose JS thread is blocked renders nothing, so commitQuiet and animationIdle read it as settled. |
| `assert` | One or more checks. See the table below. |
| `assertVisible · assertNotVisible` | A selector. The same as assert: { visible } and { notVisible }. |

### App and device

| Step | Options |
| --- | --- |
| `launchApp` | app, relaunch (ifNeeded by default, or always), clearState (wipes app data and relaunches). |
| `stopApp` | app. |
| `back` | Goes back. |
| `pressKey` | A key name, such as Enter or Home. |
| `navigate` | route: goes to a screen through your app’s navigator. |
| `openLink` | A URL or deep link. |
| `network.stub` | urlMatch, and the response: status, body, headers, method, delayMs. network.stub: clear removes all stubs. Needs --capture-net. |
| `hideKeyboard` | Reported as unsupported. |

### Reuse and control

| Step | Options |
| --- | --- |
| `runFlow` | file, and env to override its variables. |
| `repeat` | commands, and times or while (a condition). |
| `retry` | commands and maxRetries. |
| `branch` | if (a condition), then, and else. |
| `agent` | A goal, or goal with rounds, timeout and hints. Must be followed by an assert. See [Agentic flows](https://squiggle.sh/docs/agentic-flows). |
| `escalate` | reason: stops and asks the agent running the flow. Without one, it reports pending. |

## Assertions

`assert`, `goal`, `when` and `branch` take these. Numbers compare with `lt`, `lte`, `gt`, `gte` or `eq`.

| Check | Form |
| --- | --- |
| `visible · notVisible · exists` | { visible: <selector> } |
| `route` | { route: OrderConfirmation } |
| `rerenders` | { rerenders: { CartRow: { lt: 3 } } } |
| `droppedFrames` | { droppedFrames: { lt: 2 } } |
| `store` | { store: { useCart.mutationCount: { gte: 1 } } }: a Zustand store by the variable it is declared as, and how many times it changed. { store: { useCart: {} } } checks that it exists. |
| `query` | A TanStack Query, its status or fetch count. |
| `log` | { log: { contains: … } }, { log: { regex: … } } or { log: { field: …, equals: … } } |
| `action` | { action: { checkout.submit: { eq: 1 } } } |
| `visual` | noOverlap, noClipping, onScreen, noBlankFrame or legibleContrast, as a name or a list. |
| `memoryDelta` | { memoryDelta: { lt: 20MB } }, across the step before. Bytes, or a size in KB, MB or GB. |
| `scroll` | { scroll: { fps, droppedFrames, blank, churn, list } }, after a scroll step. Every part is optional: fps and droppedFrames default to the screen’s budget, so scroll: {} is valid. |
| `smooth` | { smooth: { region: <selector>, gapFrames, droppedFrames, jsDriven } }, after the step that starts the motion. region is required; droppedFrames defaults to the screen’s budget. |
| `memory · network · gesture` | Accepted, and reported as pending. |

## Steps Squiggle reads but doesn’t run

These Maestro commands load without an error and are reported as `unsupported`: `eraseText`, `copyTextFrom`, `pasteText`, `setLocation`, `travel`, `setOrientation`, `setAirplaneMode`, `toggleAirplaneMode`, `addMedia`, `setPermissions`, `clearKeychain`, `takeScreenshot`, `startRecording`, `stopRecording`, `runScript`, `evalScript`, `assertWithAI`, `assertNoDefectsWithAI`, `extractTextWithAI` and `assertScreenshot`. See [Coming from Maestro](https://squiggle.sh/docs/flows/maestro).
