Expressions
Reference runtime data, workflow props, trigger payloads, node outputs, loop variables, and inputs inside action payloads.
Expressions let workflow nodes use data from triggers, props, inputs, prior nodes, loops, and chain context. B3OS resolves expressions recursively inside node payloads and preserves JSON types when the whole value is an expression.
Common Context
| Expression | Meaning |
|---|---|
{{$trigger}} | Full trigger payload |
{{$trigger.body}} | Inbound webhook or event body |
{{$props.key}} | Workflow prop named key |
{{$inputs.key}} | Workflow or block input named key |
{{$nodes.nodeId.result}} | Result of a prior node |
{{$nodes.nodeId.error}} | Sanitized error for a prior failed node |
{{$workflow.id}} | Current workflow ID |
{{$item}} | Current loop item |
{{$index}} | Current loop index |
{{$parent_item}} | Parent loop item inside nested loops |
{{$for.nodeId.item}} | Item for a specific loop node |
Type Preservation
When the entire field is an expression, B3OS preserves the referenced value type:
json{ "amount": "{{$nodes.quote.result.amount}}", "metadata": "{{$trigger.body.metadata}}", "enabled": "{{$props.enabled}}"}
If metadata is an object and enabled is a boolean, they remain objects and booleans. If an expression is embedded inside a longer string, the result is a string.
json{ "message": "Received {{$trigger.body.amount}} {{$trigger.body.asset}}"}
Trigger Data
Webhook and event triggers usually populate body, headers, and source-specific metadata:
json{ "asset": "{{$trigger.body.asset}}", "requestId": "{{$trigger.headers.x-request-id}}", "source": "{{$trigger.source}}"}
Node Outputs
Name nodes intentionally and use stable IDs. A downstream node can reference any prior node result:
json{ "channel": "alerts", "text": "Price is {{$nodes.fetchPrice.result.price}} for {{$props.asset}}"}
Loop Variables
Inside a loop body, B3OS exposes loop variables:
| Variable | Meaning |
|---|---|
{{$item}} | Current item |
{{$index}} | Zero-based index |
{{$parent_item}} | Parent loop item in nested loops |
{{$parent_index}} | Parent loop index |
{{$for.loopNodeId.item}} | Specific loop item by loop node ID |
{{$for.loopNodeId.index}} | Specific loop index by loop node ID |
{{$item}} is convenient in a single loop. In nested loops, {{$for.loopNodeId.item}} is easier to read and safer to maintain.
Conditions and Filters
Branch and filter-style payloads can evaluate values from context. Use early filtering to reduce provider calls and avoid unnecessary wallet operations.
json{ "condition": "{{$trigger.body.amount}} > 100", "then": "large-payment", "else": "standard-payment"}
For item-level filters, item fields can be referenced by the active item context:
json{ "items": "{{$nodes.fetchBalances.result.items}}", "where": "{{$item.usdValue}} > 10"}
Secrets and Sensitive Values
Expressions can technically reference any data available to a run, but secrets should not be passed through visible payloads.
Put provider tokens in connectors, machine credentials in API keys or service accounts, and signing authority in wallets. Do not store secrets in props, workflow inputs, or arbitrary JSON payloads.
Debugging Expressions
- Test with a manual run and representative input.
- Inspect the node input after expression resolution.
- Inspect the prior node output shape.
- Confirm the node ID in the expression matches the graph.
- Check whether the value is an object, array, number, boolean, string, or null.
- Use a
format,regex, orcode-transformnode to normalize data before passing it to strict action schemas.
