Workflow Settings Schema
A workflow definition is stored as a WorkflowSettings document containing a version string and an ordered array of Node objects. This page is the reference for the raw JSON shape of workflow definitions — useful when building integrations, writing toolkit scripts, or constructing workflows programmatically.
WorkflowSettings
The top-level container for a workflow definition.
| Field | Type | Required | Description |
|---|---|---|---|
version |
string |
No | Optional version tag for your own change tracking |
nodes |
Node[] |
Yes | Ordered array of all nodes in the workflow |
Workflow definitions are stored under settings.model.events for component-level workflows, or under extensions[].workflows for extension-level workflows.
Node
Each node represents a single step in the workflow graph.
| Field | Type | Required | Description |
|---|---|---|---|
id |
number |
Yes | Unique integer identifier within this workflow |
name |
string |
Yes | Human-readable label; also used as the event name for event type nodes |
type |
string |
Yes | Node category — see Node Types |
action |
string |
Yes | Execution action — typically matches type for operation nodes |
component |
string |
No | Component reference (slug or UUID) the node operates on |
data |
NodeData |
Yes | Type-specific configuration — see NodeData |
connections |
NodeConnection[] |
Yes | Outgoing connections to downstream nodes |
parallel |
boolean |
No | Whether connected child nodes execute concurrently (default: false) |
paused |
boolean |
No | When true, this node (and a recycle trigger) is skipped during execution |
x |
number |
No | Canvas x-coordinate (visual only) |
y |
number |
No | Canvas y-coordinate (visual only) |
Node Types
type |
Category | Description |
|---|---|---|
event |
Trigger | Listens for a model CRUD event and starts the workflow |
recycle |
Trigger | Timer-based trigger; runs on a fixed schedule |
expression |
Operation | Executes a JavaScript expression in the sandbox |
condition |
Control Flow | Branches based on a boolean expression |
switch |
Control Flow | Fans out across multiple named paths |
each |
Control Flow | Iterates over a collection, executing children per item |
NodeData
NodeData is a free-form object whose keys vary by node type.
Event node
The event name on the name field determines which model event triggers this node. For multi-schema components prefix the schema name:
Create → default schema
premium::Create → premium schema
Before::Update → default schema, synchronous
Recycle node
{
"type": "event",
"action": "recycle",
"name": "Daily job",
"data": {
"timer": "1d",
"ruleset": "active = true"
}
}
| Field | Type | Description |
|---|---|---|
timer |
string |
Schedule interval — see available values |
ruleset |
string |
Optional filter expression; only matching records are loaded |
Expression node
{
"type": "operation",
"action": "expression",
"name": "Process Order",
"data": {
"expression": "io.pipe({ id: $models[0].uuid })"
}
}
| Field | Type | Description |
|---|---|---|
expression |
string |
JavaScript code executed in the sandbox |
Condition node
{
"type": "condition",
"action": "condition",
"name": "Is Active?",
"data": {
"expression": "if ($models[0].active) { resolver.resolve() } else { resolver.reject() }"
}
}
| Field | Type | Description |
|---|---|---|
expression |
string |
JavaScript code that calls resolver.resolve() or resolver.reject() |
Switch node
{
"type": "switch",
"action": "switch",
"name": "Route by Type",
"data": {
"expression": "follow.path($models[0].type)"
}
}
| Field | Type | Description |
|---|---|---|
expression |
string |
JavaScript code that calls follow.path(name) one or more times |
Each node
The each node does not require any data fields. It reads the collection provided by io.each(array) from its upstream node and executes child nodes once per item.
NodeConnection
Connections define directed edges between nodes.
| Field | Type | Required | Description |
|---|---|---|---|
id |
number |
Yes | Unique integer identifier for this connection |
source |
ConnectionPoint |
Yes | The output slot on the source node |
destination |
ConnectionPoint |
Yes | The input slot on the destination node |
type |
string |
Yes | Connection type — "pass", "reject", or a named switch path |
name |
string \| null |
No | Human-readable label (optional) |
Connection types
type |
Used by | When followed |
|---|---|---|
pass |
All nodes, Condition (resolve) |
Default forward path |
reject |
Condition | resolver.reject() was called |
<path name> |
Switch | follow.path(name) was called with this name |
ConnectionPoint
| Field | Type | Description |
|---|---|---|
id |
number |
Matches the id of the source or destination node |
position |
string |
Visual attachment side — "top", "bottom", "left", or "right" |
Complete Example
A workflow that runs on Create, checks if the order is above a threshold, and either sends a notification or logs a skip:
{
"version": "1.0",
"nodes": [
{
"id": 1,
"name": "Create",
"type": "event",
"action": "event",
"data": {},
"connections": [
{
"id": 10,
"source": { "id": 1, "position": "bottom" },
"destination": { "id": 2, "position": "top" },
"type": "pass",
"name": null
}
],
"parallel": false,
"paused": false,
"x": 100,
"y": 50
},
{
"id": 2,
"name": "High Value?",
"type": "condition",
"action": "condition",
"data": {
"expression": "if ($models[0].total > 1000) { resolver.resolve() } else { resolver.reject() }"
},
"connections": [
{
"id": 11,
"source": { "id": 2, "position": "bottom" },
"destination": { "id": 3, "position": "top" },
"type": "pass",
"name": null
},
{
"id": 12,
"source": { "id": 2, "position": "right" },
"destination": { "id": 4, "position": "top" },
"type": "reject",
"name": null
}
],
"parallel": false,
"paused": false,
"x": 100,
"y": 200
},
{
"id": 3,
"name": "Notify Team",
"type": "operation",
"action": "expression",
"data": {
"expression": "await Component('notifications').create({ type: 'high_value_order', ref: $models[0].uuid })"
},
"connections": [],
"parallel": false,
"paused": false,
"x": 0,
"y": 380
},
{
"id": 4,
"name": "Skip",
"type": "operation",
"action": "expression",
"data": {
"expression": "return"
},
"connections": [],
"parallel": false,
"paused": false,
"x": 200,
"y": 380
}
]
}
Node IDs
Node id values must be unique integers within a single workflow definition. They are used by NodeConnection to wire source and destination. There is no required ordering or continuity — you can use any integers as long as they are unique.