Skip to content

Values & Expressions

Three kinds of interpolation, all written with ${...}:

FormWhenExample
${path}Reference another payload value"${products.rows}"
${= expression}Typed expression"${= len(products.rows)}"
${secret:path}Pre-resolved secret"${secret:db/warehouse:password}"

As an entire value, ${products.rows} preserves the original type — if the field is a list, you get a list, not a stringified list. Inside a string it is stringified and treated as formatting:

Terminal window
with:
Items: "${products.rows}" # list stays a list
Subject: "Report with ${= len(products.rows)} rows" # stringified into the sentence

This distinction is the source of a common bug — see Common Pitfalls. Rule of thumb: use ${path} for whole-value references; reserve ${= …} for actual expressions.

Expressions support arithmetic, comparison, logical operators, and a library of built-ins:

Terminal window
len() str() int() float() bool()
upper() lower() trim() contains()
isEmpty() toJson() split() join()
path() field() first() keys() replace()
now() today() dateAdd() dateFormat() dateDiff()
Terminal window
with:
Total: "${= price * quantity }"
Tag: "${= upper(trim(category)) }"
Empty: "${= isEmpty(items) }"
Json: "${= toJson(order) }"

That’s 23 of the 38 registered built-ins. The rest — min/max, startsWith/endsWith, concat, abs/round, not, take/skip, omit, values/entries, zip, shellEscape — are covered with full signatures in the Flow Language Reference.

http.* connectors auto-parse their response body into a structured value whenever the response carries a JSON Content-Type — dotted access like ${weather.body.temp} works directly, the same as any other connector output:

Terminal window
using:
- http
output:
temperature: !num
steps:
- $weather:
invoke: http.get
with:
Hosts: ["https://api.open-meteo.com"]
Url: "https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current=temperature_2m"
- return:
temperature: "${weather.body.current.temperature_2m}"

Without a JSON Content-Type (XML, plain text, a missing header, or malformed JSON), body stays raw text and dotting into it fails — ${weather.body.current.temperature_2m} errors with 'body' is not an object. path(data, jsonpath) is the general tool for that case, and for genuine JSONPath queries (multiple matches, wildcards, filters) even on a body that’s already auto-parsed:

Terminal window
- return:
temperature: "${= path(weather.body, '$.current.temperature_2m')}" # works whether body is raw text or already parsed

data can be a JSON string, an already-structured value, or an XML string (auto-detected from a leading <). A JSONPath matching no node returns null; malformed input fails the step. One match returns that value with its native type — a JSON number comes back as a number, so no float()/int() cast is needed unless the target field is declared !str. A JSONPath matching several nodes returns an array.

To write a literal ${foo} without resolution, escape it with a doubled dollar:

Terminal window
with:
Template: "$${foo} stays literal"

Conditions in if: and switch: are plain expressions — they are already in expression context, so they take no ${= … } wrapper:

Terminal window
- if: count > 0 # correct
# - if: "${= count > 0}" # wrong — double-evaluated

The full grammar — operator precedence, every built-in, every diagnostic — lives in the Flow Language Reference.