Flow syntax reference
For AI agents: this page is Markdown at https://squiggle.sh/docs/reference/flow.md. Every docs page is listed at https://squiggle.sh/llms.txt, and all of them are one file at https://squiggle.sh/llms-full.txt.
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-language-server: $schema=https://squiggle.sh/schemas/flow-1.0.0.schema.jsonflow: checkoutTop-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. |
healing | adaptive (default), strict or lossy-fallback. See 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. |
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.
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. |
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.