Data Flow & Templates
Template syntax, the data bucket, type preservation, built-in functions, file references.
How Data Flows
When a node completes, its output data is:
- Stored in the data bucket under the node's label (display name).
- Merged into each child node's input under the parent's label.
- Available to all downstream nodes via template references, not just direct children.
Example Flow
[Email Input] → [Extract Order] → [Calculate Quote] → [Send Quote]
After Email Input completes with {"sender": "john@example.com", "body": "..."}:
- The bucket contains:
{"Email Input": {"sender": "john@example.com", "body": "..."}} Extract Orderreceives this as input.
After Extract Order completes with {"Order Request": {"weight_lbs": 5000}}:
- The bucket contains both nodes' outputs.
Calculate Quotecan reference bothEmail InputandExtract Order.
After Calculate Quote completes:
Send Quotecan reference all three upstream nodes.
Template Syntax
Templates use double curly braces: {{expression}}
Path References
The primary form references a node's output field:
{{input["Node Label"]["field_name"]}}Bracket notation (recommended):
{{input["Email Input"]["sender"]}}
{{input["Extract Order"]["Order Request"]["weight_lbs"]}}
{{input["Calculate Quote"]["output"]["total_price"]}}Nested fields:
{{input["Node"]["address"]["city"]}}
{{input["Node"]["items"][0]["name"]}}Array elements:
{{input["Node"]["items"][0]}} -- first element
{{input["Node"]["items"][1]}} -- second elementFile References
Files are under a special "files" key:
{{input["files"]["Node Label"][0]["name"]}} -- filename
{{input["files"]["Node Label"][0]["url"]}} -- file URL
{{input["files"]["Node Label"][0]["size"]}} -- size in bytesEscaped Quotes in JSON
When templates appear inside JSON config values, the quotes inside the template are escaped:
{
"url": "https://api.example.com/{{input[\"Node\"][\"id\"]}}"
}The engine automatically unescapes these before resolution.
Built-in Functions
Functions use the syntax: {{functionName(arg1, arg2)}}
Arguments can be:
- String literals:
"quoted text" - Path references:
input["Node"]["field"] - Nested function calls:
lower(input["Node"]["name"])
Function Reference
| Function | Signature | Description | Example | Result |
|---|---|---|---|---|
uuid | uuid() | Generate a UUID v4 | {{uuid()}} | "a1b2c3d4-e5f6-..." |
now | now("format") | Current time in Go format | {{now("2006-01-02")}} | "2026-04-12" |
lower | lower(value) | Lowercase string | {{lower("HELLO")}} | "hello" |
upper | upper(value) | Uppercase string | {{upper("hello")}} | "HELLO" |
length | length(value) | Count elements in array, string, or map | {{length(input["N"]["items"])}} | 3 |
join | join(array, separator) | Join array elements | {{join(input["N"]["tags"], ", ")}} | "a, b, c" |
default | default(value, fallback) | Return fallback if value is nil or empty | {{default(input["N"]["x"], "N/A")}} | "N/A" |
toJSON | toJSON(value) | Serialize to JSON string | {{toJSON(input["N"]["data"])}} | "{\"key\":\"val\"}" |
fileExtension | fileExtension(path) | Extract file extension | {{fileExtension("doc.pdf")}} | ".pdf" |
now() Format Strings
Go uses a reference time for format strings. The reference time is:
Mon Jan 2 15:04:05 MST 2006
Common formats:
| Format String | Output |
|---|---|
"2006-01-02" | 2026-04-12 |
"2006-01-02T15:04:05Z" | 2026-04-12T14:30:00Z |
"01/02/2006" | 04/12/2026 |
"January 2, 2006" | April 12, 2026 |
"15:04:05" | 14:30:00 |
"Mon 3:04 PM" | Sat 2:30 PM |
Nesting Functions
Functions can be nested:
{{lower(fileExtension(input["Node"]["filename"]))}}This extracts the extension and lowercases it: ".PDF" -> ".pdf".
{{default(lower(input["Node"]["name"]), "unknown")}}If name is nil, returns "unknown". If name is "JOHN", returns "john".
Type Preservation
Whole-Value Replacement
When the entire JSON value is a single template, the resolved type is preserved:
{
"count": "{{input[\"Node\"][\"count\"]}}",
"active": "{{input[\"Node\"][\"active\"]}}",
"items": "{{input[\"Node\"][\"items\"]}}"
}If count is 42 (number), active is true (boolean), and items is [1,2,3] (array), the result is:
{
"count": 42,
"active": true,
"items": [1, 2, 3]
}Partial Interpolation
When a template is part of a larger string, the result is always a string:
{
"message": "Order has {{input[\"Node\"][\"count\"]}} items"
}Result: {"message": "Order has 42 items"} (string).
Implications
- Use whole-value templates when you need numbers, booleans, or objects downstream.
- Use partial interpolation for building strings (email bodies, URLs, messages).
Unresolved Templates
If a path cannot be resolved (node doesn't exist, field is missing), the template is left as-is:
{{input["Missing Node"]["field"]}}Remains literally {{input["Missing Node"]["field"]}} in the output.
The engine generates warnings for unresolved references that include suggestions for close matches (using fuzzy matching).
Variable Name Sanitization
The engine performs case-insensitive matching with name sanitization:
- Hyphens and spaces are converted to underscores
"Order-Request"matches"Order_Request"or"order_request"
This provides resilience against minor naming inconsistencies, particularly with LLM-generated field names that may normalize to snake_case.
Conditional Node References
Conditional nodes use a slightly different reference syntax within their condition fields:
In Condition Fields
Dot notation:
NodeName.fieldName NodeName.nested.field
Bracket notation:
input["NodeName"]["field"]
String literals (quoted):
"literal value"
Interpolated strings:
"Hello {NodeName.firstName}"
"user-{NodeName.id}@example.com"{...} for interpolation within quoted strings, not double curly braces.Loop Data Context
Inside a loop body, template references have additional context:
Data Loop
{{input["Loop Node Label"]["data"]}} -- current iteration item
{{input["Loop Node Label"]["parent"]}} -- parent data (if includeParent)File Loop
{{input["Loop Node Label"]["__loop_file_name__"]}} -- current file name
{{input["Loop Node Label"]["__loop_file_link__"]}} -- current file URL
{{input["Loop Node Label"]["__loop_file_size__"]}} -- current file sizeSequential Loop Iteration History
In sequential loops, prior iterations' results are available:
{{input["Loop Label_iter_0"]["field"]}} -- iteration 0 output
{{input["Loop Label_iter_1"]["field"]}} -- iteration 1 outputThis enables sequential accumulation patterns where each iteration builds on the previous.
File Reference Patterns
Files are referenced in node configs (particularly output and integration nodes) using these patterns:
All Files from a Node
input["files"]["Node Label"]
Specific File by Index
input["files"]["Node Label"][0] -- first file input["files"]["Node Label"][1] -- second file
File Properties
Each file object has:
| Property | Description |
|---|---|
name | Original filename |
url | Storage URL |
size | Size in bytes |
In Output Node Config
Gmail and integration nodes accept fileReferences as an array of reference strings:
{
"fileReferences": [
"input[\"files\"][\"Email Input\"]",
"input[\"files\"][\"OCR Node\"][0]"
]
}Duplicate Filename Handling
When multiple files have the same name, behavior is controlled by duplicateFilenameBehavior:
| Value | Behavior |
|---|---|
auto_suffix | Append _1, _2, etc. to duplicates |
error | Fail if duplicates exist |
last_wins | Keep only the last occurrence |
Out of Bounds Handling
When a file index doesn't exist, behavior is controlled by outOfBoundsBehavior:
| Value | Behavior |
|---|---|
error | Fail if index is out of bounds |
skip | Silently skip the missing file |
Data Bucket Snapshot
At any point during execution, the full bucket can be inspected. It contains every completed node's output keyed by label:
{
"Email Input": {
"id": "msg_abc123",
"thread_id": "thread_xyz789",
"sender": "buyer@example.com",
"subject": "Steel Order Request",
"body": "Need 5000 lbs carbon steel..."
},
"Extract Order": {
"Order Request": {
"customer_name": "John Smith",
"weight_lbs": 5000,
"steel_type": "carbon"
}
},
"Calculate Quote": {
"output": {
"total_price": 2250.00,
"price_per_lb": 0.45
}
}
}Files are tracked separately:
{
"files": {
"Email Input": [
{
"name": "spec-sheet.pdf",
"url": "https://storage.example.com/files/...",
"size": 245760
}
]
}
}Common Patterns
Forward a Field
{{input["Upstream Node"]["field"]}}Build a URL with Dynamic Segments
https://api.example.com/orders/{{input["Node"]["order_id"]}}/statusConditional Default
{{default(input["Node"]["optional_field"], "fallback_value")}}Generate a Unique Filename
report-{{uuid()}}-{{now("20060102")}}.pdfSerialize Data for an API Call
{
"payload": "{{toJSON(input[\"Extract Order\"][\"Order Request\"])}}"
}Access Loop Iteration Data in Loop Body
{{input["Process Orders"]["data"]["order_id"]}}Reference the Thread ID for Gmail Reply
{{input["Email Input"]["thread_id"]}}