Expressions
{{ }} interpolation with $trigger/$nodes/$steps/$workflow/$config.
Expressions let workflow node config fields reference data produced by earlier steps. They use {{ }} interpolation with five canonical namespaces, and optionally the = sentinel prefix.
Sentinel prefix
The canonical form of an expression is the = sentinel followed by a {{ }} body:
={{ $trigger.data.issueId }}The sentinel tells the platform "evaluate this as an expression, not a literal string." Bare {{ }} without the sentinel is also accepted during a deprecation window - existing workflows with this form continue to work - but new authoring should use the sentinel. The linter flags bare forms.
Namespaces
Five namespaces are available inside {{ }}:
Prop
Type
$env, $now, $today, $execution, and $runIndex are not supported and will throw at runtime. Credentials never flow through expressions - they are wired through node graph composition and the credential provider.
Path syntax
Expressions support dot notation, bracket-string, bracket-number, and dynamic bracket access:
$trigger.data.issue.labels[0]
$config.projects["my-project"].repoPath
$nodes.fetch-details.output.items[2].name
$nodes.multi.output[$trigger.data.index]- Dot notation -
$trigger.data.issueId - Bracket-string -
$config.projects["key"](single or double quotes) - Bracket-number -
$nodes.results.output.items[0] - Dynamic bracket - the inner expression is resolved recursively; max depth 10
Forbidden keys (__proto__, constructor, prototype) are rejected at parse time.
Type preservation
A single-token expression (the entire string is one {{ }} block) preserves the native JavaScript type of the resolved value:
config:
# Returns the raw array, not a JSON string
labels: "={{ $trigger.data.labelIds }}"
# Returns the number 3, not "3"
priority: "={{ $trigger.data.priority }}"A multi-token or mixed-text expression stringifies non-string values and concatenates:
config:
subject: "Issue {{ $trigger.data.identifier }}: {{ $trigger.data.title }}"
# ^^^^^^^ literal ^^^^^^^^^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^ second tokenBoolean and comparison expressions (v2 evaluator)
Condition nodes and when guards accept a richer boolean expression syntax that supports comparison and logical operators. The evaluator uses a safe hand-rolled parser - no eval, no Function constructor:
config:
when: "={{ $nodes.check.output.count < 8 }}"
# or with logical operators:
guard: "={{ $nodes.status.output.approved && $config.featureFlag }}"Supported operators (lowest to highest precedence):
| Level | Operators |
|---|---|
| Logical OR | || |
| Logical AND | && |
| Equality | == === != !== |
| Relational | < <= > >= |
| Additive | + - |
| Multiplicative | * / % |
| Unary | ! - |
Literals: numbers, single/double-quoted strings, true, false, null, undefined.
Path leaves resolve through the same namespace allowlist, so $env.X inside a boolean expression is also rejected.
Lenient mode (test runs)
During test-run mode the evaluator runs in lenient mode. Missing references resolve to a placeholder string (<test:expr>) instead of throwing, so the test can continue even when upstream mock data is incomplete.
Common patterns
# Simple field access
issueId: "={{ $trigger.data.issueId }}"
# Node output
branchName: "={{ $nodes.create-branch.output.branchName }}"
# Mixed text
prTitle: "fix: {{ $nodes.analyze.output.summary }} ({{ $trigger.data.identifier }})"
# Array index
firstLabel: "={{ $trigger.data.labelIds[0] }}"
# Conditional guard on a condition node
when: "={{ $nodes.counter.output.value < 5 }}"
# Workflow self-reference
logMsg: "Running workflow {{ $workflow.name }} instance {{ $workflow.instanceId }}"Editor support
The SchemaPicker in the node config panel provides a variable tree populated from upstream node output schemas. Click the variable icon next to any expression field to browse available paths and insert them. The SchemaPicker only offers paths that this evaluator will accept.
Feature flag note
Workspace-level feature flags (see Environments) are resolved by the platform before expressions run. They are not accessible via $config - they control dispatch routing, not expression data.
Related pages
- Triggers -
$triggernamespace values and output contracts - Node Config Panels - SchemaPicker M2
- Validation - expression discipline checks
- Testing - lenient mode during test runs