Expression Syntax
Every field in a node's configuration can contain plain text, a single expression, or a template string mixing text and expressions. This page documents the exact syntax, all directive types, accessor patterns, evaluator kinds, and escaping rules.
The Two Formats
There are two syntax forms for expressions. The form used determines whether the full field value is replaced by the resolved value, or whether the resolved value is interpolated into a larger string.
Template Mode — @{directive:key}
The default mode. An expression directive can appear anywhere in a string field. Multiple directives can appear in a single field. The resolved value is interpolated in-place as a string.
"Hello, @{input:firstName} @{input:lastName}"
"https://@{env:API_HOST}/v1/endpoint"
"Token: @{secret:ServiceApiKey}"
When a template expression resolves to a non-string (number, boolean, object), the value is converted to a string for interpolation. If the entire field contains only a single expression with no surrounding text, the resolved type is preserved without conversion — see type coercion below.
Strict Mode — {{directive:key}}
Double-brace syntax. In strict mode the entire field must consist of exactly one expression — no surrounding text is permitted. The resolved value is returned with its original type preserved (no string conversion). Use strict mode when a field expects a non-string type such as a number, boolean, array, or object.
{{input:maxRetries}} → resolves to integer 3, not "3"
{{output:fetchNode.items}} → resolves to the original array
{{var:isEnabled}} → resolves to boolean true
Directive Types
| Directive | Resolves from | Evaluation Tier | Example |
|---|---|---|---|
input | Element-level InputData bag (mapped from upstream outputs) | Tier 2 | @{input:employeeId} |
output | NodeOutputs in ExecutionMemory, keyed by element key | Tier 2 | @{output:fetchUser.email} |
var | Variable scope chain (global + loop/function scopes) | Tier 2 | @{var:approvalThreshold} |
env | Process environment variables | Tier 1 | @{env:DATABASE_URL} |
secret | Vault / secrets store | Tier 1 | @{secret:StripeApiKey} |
context | Execution context (tenant, process, thread IDs) | Tier 1 or Tier 2 | @{context:tenantId} |
The Output Directive — Dot-Path Accessor
The output directive supports a dot-path accessor to navigate into nested
objects. After the element key, append a dot-separated path to the value you need.
@{output:elementKey} → full output dictionary
@{output:elementKey.fieldName} → top-level field
@{output:elementKey.user.email} → nested: output.user.email
@{output:elementKey.items[0].id} → array index access
The path is evaluated using a simple recursive descent over the resolved object.
If any segment in the path does not exist or the value at that point is null,
the expression resolves to null (not an error). Use the ?? default operator
in a JavaScript evaluator if you need a fallback value.
Three Evaluator Kinds
Beyond simple directive substitution, a field can declare a different
EvaluatorKind via the NodeFieldDescriptor. This
determines how the field value is processed after directives are resolved.
Template
The default. Directive placeholders are substituted into the string, then the result is returned as-is. No further interpretation. Suitable for most config fields.
JavaScript (Jint)
The field value is a JavaScript expression evaluated via the Jint interpreter. The resolved directive values are injected as named variables. Supports complex expressions: conditionals, arithmetic, string operations, array methods.
JSONPath
The field value is a JSONPath query applied against a specified source object (typically a node output or variable). Returns the matched values. Used for extracting data from complex JSON payloads.
JavaScript Evaluator — Available Context
When EvaluatorKind.JavaScript is used, the Jint sandbox exposes the
following named identifiers. Any directive that was resolved in Tier 1 or Tier 2
is also injected by its key.
| Identifier | Contents |
|---|---|
input | Element-level InputData dictionary |
output | Full NodeOutputs dictionary (all nodes) |
vars | Current variable scope (read-only) |
ctx | Execution context (tenantId, processId, threadId) |
now | Current UTC timestamp as ISO 8601 string |
// Example JavaScript field value:
input.amount * 1.2 > vars.threshold ? "escalate" : "approve"
// Array operations are available:
output.fetchItems.items.filter(x => x.active).map(x => x.id).join(",")
Resolution Decision Flow
Type Coercion
When a directive resolves to a non-string value and the surrounding field is a template string (not strict mode):
- Numbers and booleans — converted to their string representation:
42→"42",true→"true". - Null/undefined — converted to an empty string
"". - Objects and arrays — serialized via
JSON.Serialize. - Single expression, no surrounding text — even in template mode, when the entire field is just one expression with nothing else, the original type is preserved (no conversion). This matches the strict mode behaviour.
Escaping
To include a literal @{ in a field value without it being interpreted as
an expression, double the @ sign:
@@{input:key} → literal text: @{input:key}
@@{env:PATH} → literal text: @{env:PATH}
This is only needed if you are constructing a field value that should contain the expression syntax as literal text — for example, generating a template string that will be evaluated again downstream.
Nested Expressions
Expressions cannot be nested — you cannot put an expression inside another expression's key. If you need to compute a key dynamically, use the JavaScript evaluator, which gives you full programmatic access to all bags.
OnProcess. Expressions exist solely to wire runtime data into static
configuration fields.