For the curious. Your AI agent handles template variables automatically. This explains how data flows between nodes and what’s happening when you see
${variable} syntax in workflows or traces.${variable} syntax in a workflow, it’s pulling data from previous nodes, workflow inputs, or nested structures.
Basic syntax
The${variable} syntax accesses values from the shared store:
${read.content} pulls the content output from the read node.
Nested access
Template variables can traverse deeply nested structures:apinode’s outputresponsekeyitemsarray- First element (
[0]) namefield
Type preservation
Template variables preserve the original data type when used alone. When combined with text, they become strings.
Simple templates (just
${var}) preserve type. Complex templates (any surrounding text) become strings.
Inline objects
This type preservation makes inline object construction intuitive:settings is {"timeout": 30} and results is {"status": "ok"}, the resolved stdin is:
JSON auto-parsing
When a template accesses nested fields on a JSON string, pflow automatically parses it:fetch.stdout is a string containing JSON, the nested access ${fetch.stdout.results[0].name} works because pflow:
- Sees you’re trying to access
.results - Attempts to parse
stdoutas JSON - Traverses the parsed structure
- Returns the value at
results[0].name
any is never re-parsed. ${config.field} reads a - type: object input directly, but JSON text held in a - type: string input stays text — ${text.field} on it is a validation error (declare the input object instead).
Workflow inputs
Template variables also reference workflow inputs declared in the workflow definition:Stdin input
Inputs can receive piped data by addingstdin: true. See Stdin input for details.
Array notation
Array elements are accessed using bracket notation:${results[${__index__}].title} in a batch. The whole expression is one reference: it keeps its value’s type, and if the index does not resolve to an in-range whole number the reference is unresolved (a ?? fallback then applies).
Batch processing
In batch nodes, a special template variable (${item} by default) represents the current item:
as: "file" creates ${file} as the item variable. See Batch processing for details.
Coalesce operator
The?? operator returns the first operand that resolves, falling through whenever the left side isn’t there — whether the node didn’t run (a branch that wasn’t taken) or the referenced field is absent on a node that did run:
true, false, null, and empty []/{} (a literal string cannot contain the ?? sequence itself). A bare reference with no ?? fallback — ${node.field} — still raises an unresolved-variable error when the field is missing, so genuine typos are caught; add a fallback only where absence is expected.
Node metadata
Beyond their primary outputs, some nodes expose metadata that downstream nodes can access through templates. This is useful for cost-aware workflows, debugging, or building on execution details.LLM token usage
Bothllm and agent nodes write llm_usage with token counts:
Shell command
Ashell node’s command is the text the workflow wrote — pflow never fills in template variables there. Values reach it through env: (see Shell). When a shell node fails, the error output shows the command and its bound env: values, and pflow report shows both for every shell node.
HTTP response details
Thehttp node exposes response_headers (dict) and response_time (seconds as float) alongside the response body. Useful for handling pagination links, rate limit headers, or performance monitoring.
Escaping
Literal${...} text (not a template variable) uses double dollar signs to escape:
Price: ${PRICE} instead of trying to resolve a variable.
An escape runs through its matching }, and anything inside it is literal, including braces nested one level deep: $${NAME:-${default}} produces ${NAME:-${default}} without resolving ${default}. So shell parameter expansion ($${NAME:-world}, $${#ITEMS}) works the same way inside a templated parameter. Text after the closing } is templated as usual. Only $${ is an escape: a bare $$ is passed through unchanged.
A shell command and a code node’s code block are not templated, so they need no escape: ${NAME:-world} there is already the shell’s own. $${ in either is a validation error.
JavaScript passed to an MCP tool
In a code parameter of an MCP tool (such asevaluate_script’s function), ${...} is JavaScript and $${ is sent as written. A workflow value goes in as ${x|json}, which inserts it as a JavaScript value — a quoted, escaped string, true, a number, an object literal. |json is accepted only there. See MCP: code passed to a tool.
Validation
pflow validates template variables at workflow creation time:- Unknown variables → Error: “Unresolved variable:
${typo}” - Type mismatches → Warning: “Expected string, got dict”
- Invalid syntax → Error: “Invalid template:
${foo.}”
Compile-time vs runtime validation
Compile-time vs runtime validation
Most validation happens when the workflow is created (compile-time). Some validations happen during execution (runtime):
- Compile-time: Variable existence, type compatibility, syntax
- Runtime: JSON parsing success, nested access on dynamic values
Related
- Nodes overview - How nodes use template variables
- Batch processing - Item variables in batch nodes
- Debugging - Troubleshooting template issues

