Workflows & nodes
A workflow is a directed graph of nodes connected by edges between typed ports.
There is exactly one document format — niner.workflow.v1 — used everywhere: the Studio editor,
export/import, the Nucleus registry, and the compiler. What you export is what deploys.
{
"schemaVersion": "niner.workflow.v1",
"id": "wf_…",
"name": "My workflow",
"nodes": [ { "id": "n1", "type": "trigger.http.webhook", "config": { … }, "position": { … } } ],
"edges": [ { "sourceNodeId": "n1", "sourcePort": "output", "targetNodeId": "n2", "targetPort": "input" } ],
"triggers": [ { "nodeId": "n1" } ]
}
Nodes and ports
Every node declares its input and output ports. Most have one of each; flow-control nodes have
more — If has true/false outputs, Switch grows one output per rule, Merge takes
Input 1 … Input N, Try adds an error output. Ports are visible on the canvas and the
engine routes data per port, so a branch is a wire, not a convention.
The node reference lists every node’s ports and properties — generated from the node registry, so it can’t drift from the code.
Properties, validation, expressions
Node configuration is a flat list of typed properties (string, number, boolean, select, JSON, code, cron, rows …) rendered by one generic properties panel — with rich editors where they earn their place (cron builder, code editor, the visual JSON editor).
Every node has a verify() rule set: the editor shows warning/error badges per node before
you run (disconnected required port, invalid cron, unparseable JSON, empty code …).
Any string field can hold a {{ }} expression — real JavaScript evaluated per item at the
node boundary, in every node, in both the editor engine and compiled apps:
Order {{ $json.orderId }} for {{ $json.customer }} — {{ $json.items.length }} items
Fields with their own semantics (code, JSON editors, credential/secret fields) are excluded.
${credential.*} and ${variable.*} placeholders resolve once at run start, before expressions.
Frames: Loop and Try
Two constructs are frames — visual containers whose membership means something to the engine:
- Loop Over Items — nodes inside the frame run per batch over an item list, with batch size and concurrency settings.
- Try — an error boundary. Members run normally; if one throws, it is retried per policy and
the failure is routed to the frame’s
errorport with the exception, instead of failing the run.
Processing 1,000 items
Three mechanisms, from cheapest to most explicit:
- Whole-set (default) — list-aware nodes (Sort, Dedupe, Aggregate, Split Out, Limit, CSV …) process the entire list in one execution.
- Run once per item — a per-node toggle that maps the node over the items of its input list. Branching nodes (If/Filter/Switch) split the items across their output ports — a 100k-item list through an If yields two real item streams.
- Loop Over Items frame — when a section of the graph must iterate with explicit batching.
Sub-workflows
The Execute Workflow node runs other workflows — sequentially or in true parallel with one labelled output per child — and the debugger can step into a sub-workflow run and back.