Context & expressions
The workflow context
Every execution carries a context — a nested key/value store that node configs read from and write to. It starts with the trigger payload and grows as nodes run.
| Path | What's there |
|---|---|
trigger.* | The payload passed to Workflow::run() / dispatch() (or a webhook's request body, or a model event's attributes). |
nodes.<id>.* | The output of the node with that id, once it has run. Non-array outputs are wrapped as { "result": <value> }. |
resume.* | The payload passed to Workflow::resume($id, [...]), available to the node that was waiting. |
| (anything else) | Whatever Variable nodes write, e.g. order.status. |
Expressions: {{ ... }}
Any string value in a node's config can contain {{ expression }} markers.
They're resolved just before that node runs, against the current context.
['message' => 'Order {{ trigger.order_id }} total is {{ nodes.calc-total.result }}']
Resolution rules:
- A string that is exactly one
{{ expression }}returns the raw resolved value with its type preserved (a number stays a number, an array stays an array). - A string with
{{ }}embedded in other text interpolates — the resolved value is stringified (arrays become JSON) and spliced in. - Resolution is recursive through nested arrays in a node's config.
- An expression that doesn't resolve yields
null(embedded, it renders as an empty string) rather than throwing.
Path + method chains
An expression is a dot path optionally followed by a chain of method calls:
nodes.list-users.result.first().name.upper()
└──────── path ────────┘└ methods ──────────┘
The path is looked up in context with dot notation. Each method is then applied
to the running value. Method arguments are comma-separated literals — quoted
strings, numbers, true / false / null.
Expressions do not have arithmetic or comparison operators. Use the Condition / Switch nodes for branching, and the Math / Text nodes (or the method helpers below) for transformations.
{{ item.* }} — deferred
Inside the per-item field of a collection node
({{ item.status }}, {{ item.email }}), item is not resolved by the
engine's normal pass — the node binds each element to item itself as it
iterates. item is the only deferred keyword.
Chainable method reference
All methods are applied left to right. A method that doesn't apply to the value's type generally passes the value through unchanged; an unknown method name raises an error.
Array / collection
| Method | Result |
|---|---|
first() / last() | First / last element, or null. |
count() / length() | Element count (string length for strings). |
keys() / values() | Array keys / values. |
reverse() | Reversed array (or reversed string). |
unique() | De-duplicated, re-indexed. |
flatten() | Fully flattened. |
sum() / min() / max() / avg() | Aggregate over a numeric array. |
nth(i) | Element at index i. |
skip(n) / take(n) | Drop / keep the first n. |
pluck('key') | Column key from an array of rows. |
filter('field', 'op', value) | Keep rows where field op value holds. op ∈ == != > >= < <= in. |
join(', ') | Implode with a separator. |
sort() | Ascending sort (spaceship comparison). |
String
| Method | Result |
|---|---|
upper() / lower() | Case. |
trim(chars?) / ltrim() / rtrim() | Trim. |
camelCase() / snake_case() / kebab() / studly() / title() / slug(sep?) | Case conversions. |
limit(n, end?) | Truncate to n chars with a suffix (...). |
start(prefix) / finish(suffix) | Ensure it begins / ends with the given string. |
before(x) / after(x) | Substring before / after x. |
contains(x) / startsWith(x) / endsWith(x) | Boolean tests. |
replace(search, replace) | Literal replace. |
split(sep) | Explode to an array. |
words() | Word count. |
pad(len, char?) / repeat(n) / substr(start, len?) / indexOf(x) | As named. |
wrap(before, after?) | Surround the value. |
Number
| Method | Result |
|---|---|
add(n) / sub(n) / mul(n) / div(n) / mod(n) | Arithmetic (division / modulo by zero yields null). |
abs() / ceil() / floor() / round(precision?) | Rounding. |
clamp(min, max) | Constrain to a range. |
format(decimals?, decPoint?, thousandsSep?) | number_format. |
Type & logic
| Method | Result |
|---|---|
string() / int() / float() / bool() | Cast. |
json() | Encode to a JSON string. |
decode() | Decode a JSON string to a value. |
fallback(default) | The value, or default if it's null. |
isset() / empty() / not() | Boolean tests on the value. |
if(then, else) | then when truthy, else else. |
Date
| Method | Result |
|---|---|
date(format?) | Parse the value and format it (default Y-m-d). |
time(format?) | Same, default H:i:s. |
now(format?) | The current time formatted (ignores the value). |
Reading context directly
Custom node handle() methods receive the resolved $config array, so most
never touch the context object. When you do need it:
$context->get('nodes.list-users.result', default: []);
$context->get('trigger.email', strict: true); // throws if the path is missing
$context->set('order.status', 'shipped');
$context->all(); // the full array