Skip to main content
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.
Template variables let nodes pass data to each other without writing glue code. When you see ${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:
Here, ${read.content} pulls the content output from the read node.

Nested access

Template variables can traverse deeply nested structures:
This traverses:
  1. api node’s output
  2. response key
  3. items array
  4. First element ([0])
  5. name field

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:
If settings is {"timeout": 30} and results is {"status": "ok"}, the resolved stdin is:
Without type preservation, both would be stringified JSON requiring manual parsing.

JSON auto-parsing

When a template accesses nested fields on a JSON string, pflow automatically parses it:
Even though fetch.stdout is a string containing JSON, the nested access ${fetch.stdout.results[0].name} works because pflow:
  1. Sees you’re trying to access .results
  2. Attempts to parse stdout as JSON
  3. Traverses the parsed structure
  4. Returns the value at results[0].name
This means shell commands that output JSON work directly with template variables — no manual json.loads() needed.

Workflow inputs

Template variables also reference workflow inputs declared in the workflow definition:
When running this workflow, inputs are provided via CLI arguments:

Stdin input

Inputs can receive piped data by adding stdin: true. See Stdin input for details.

Array notation

Array elements are accessed using bracket notation:

Batch processing

In batch nodes, a special template variable (${item} by default) represents the current item:
The 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:
This is particularly useful with conditional branching where only one path executes. Without coalesce, referencing a node that didn’t run would be an unresolved variable error. Operands can also be JSON literals, used as a final default when nothing else resolves:
Literal operands accept numbers, double-quoted strings, 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

Both llm and agent nodes write llm_usage with token counts:

Shell command

The shell node stores the resolved command that was actually executed, accessible as ${node_id.command}. Useful for logging or debugging when the command is built dynamically from templates.

HTTP response details

The http 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.
Per-node cost is available internally as llm_usage.cost_usd (estimated from token pricing, null for unknown models). Access it via a code node’s inputs dict — it’s not exposed as a template variable because pricing coverage varies by model. Aggregate cost across all nodes appears in the CLI JSON output after the workflow finishes.

Escaping

Literal ${...} text (not a template variable) uses double dollar signs to escape:
This produces the literal string Price: ${PRICE} instead of trying to resolve a variable.

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.}
This works because node types declare their outputs — pflow knows at creation time what fields exist and what types they have. With arbitrary code, you’d discover these mismatches at runtime.
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
If JSON auto-parsing fails at runtime, you’ll see an “Unresolved variable” error.