Pipory
Concepts

Flow control

Branch, loop, delay, stop, sub-workflows, and error handling - how a run's shape changes based on data and failures.

Flow-control nodes don't call an API or transform data themselves - they change which nodes run next, or pause the run. See the Flow Control category in the node reference for the one-line version.

Branch (IF)

The Branch node evaluates a condition against the run context and routes execution down a true or false output handle. Nodes on the untaken branch are pruned - they never run, and report a skipped status on the canvas rather than being silently ignored. It has two modes:

Rules mode compares a left and right value (both Handlebars-resolved) with one of these operators: eq, neq, contains, gt, lt, exists, regex. Multiple conditions combine with AND or OR.

Webhook -> Branch (webhook.amount gt 100)
  true  -> Slack "Big order!"
  false -> Slack "Standard order"

AI condition mode replaces the rules with a plain-English statement - "the email sounds urgent", "this looks like a support request, not sales" - resolved against the context (any {{variable}} you write into the statement is templated first) and judged true or false by Claude, using your own Anthropic credential. A judgment failure (missing credential, API error, unparseable response) never fails the run - it routes down the false handle with the reason attached.

Loop

The Loop node points at an array in the context (a Handlebars path) and runs everything connected to its loop output once per item, with {{item}} and {{index}} available in that iteration's context. Once every item has run, execution continues from the done output with the per-item results collected into {{results}}. Iteration is capped at 100 items per run to guard against runaway loops over large arrays.

Delay

Pauses the run either for a duration or until a specific moment:

  • Duration mode - 30s, 5m, 2h, 1d, or a bare number (treated as seconds).
  • Until mode - waits until a datetime resolved from the context (a Handlebars expression or a literal ISO string), for example {{trigger.scheduledFor}}.

Either way the wait is durable: it survives app restarts because the timer is owned by the execution engine (Inngest step.sleep / step.sleepUntil), and the execution's status stays RUNNING for the whole wait rather than looking stuck.

Stop

Ends the run at this point with a chosen outcome - success or failed - and a templated message. Everything downstream is skipped. Unlike a thrown error, reaching a Stop node is never itself a failure unless you set the outcome to failed; it's a clean, deliberate exit (an early "nothing more to do here" or a "this should count as a failure" without writing an error).

Sub-Workflow

Invokes another workflow as a step from within this one, with a Handlebars-templated input mapping. Set it to wait for the child to finish (polling its execution, up to a minute) and use its output downstream, or fire it and move on without waiting. This is how you turn a common sequence into a reusable building block instead of copy-pasting it into every workflow that needs it.

Error handling: retries and the error edge

Every node supports an optional retry configuration: a retry count (capped at 5) and a backoff duration between attempts (1 second by default). With no retry configured, a node that throws fails the run immediately - the same behavior as before retries existed.

If a node has an error output edge connected, a throw that survives all retries follows that edge instead of failing the run, with the error available downstream as {{error}}. Without an error edge, the node's failure fails the whole execution.

HTTP Request (retries: 2, backoff: 1000ms)
  main  -> Slack "Success"
  error -> Slack "Failed: {{error}}"

Webhook Response

Not a control-flow node in the branching sense, but it does change what a webhook-triggered workflow returns to its caller: add one after your logic, template a status code and body, and the original HTTP request gets that response back synchronously (or a 202 with the execution id if the run takes longer than the route's 10-second wait). See Triggers.

Approval

Pauses the run for a human decision. Configure a message and a notification channel (email or Slack); the node sends a link carrying signed approve/reject tokens and the run waits. Approving routes to the approved handle, rejecting routes to the rejected handle, and an optional timeout auto-rejects if nobody responds in time - so a stalled approval doesn't leave the execution hanging forever.

AI Transform -> Approval (notify: Slack)
  approved -> Postgres "mark record approved"
  rejected -> Slack "flagged for review"

Cancel and concurrency

Two safety controls live outside the canvas, on the execution and workflow settings:

  • Cancel - stop a RUNNING execution from its detail view. The run is marked cancelled immediately and stops advancing to further nodes.
  • Concurrency cap - set a maximum number of simultaneous runs on a workflow's settings; once the cap is hit, additional runs queue and wait for a slot rather than piling load onto whatever the workflow calls out to.
  • Workflows - how context flows between nodes.
  • Executions - seeing exactly which branch was taken, or which node failed and why.