Values & Expressions
Three kinds of interpolation, all written with ${...}:
| Form | When | Example |
|---|---|---|
${path} | Reference another payload value | "${products.rows}" |
${= expression} | Typed expression | "${= len(products.rows)}" |
${secret:path} | Pre-resolved secret | "${secret:db/warehouse:password}" |
Type preservation — the key distinction
Section titled “Type preservation — the key distinction”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:
with: Items: "${products.rows}" # list stays a list Subject: "Report with ${= len(products.rows)} rows" # stringified into the sentenceThis distinction is the source of a common bug — see Common Pitfalls. Rule of thumb: use ${path} for whole-value references; reserve ${= …} for actual expressions.
Expression built-ins
Section titled “Expression built-ins”Expressions support arithmetic, comparison, logical operators, and a library of built-ins:
len() str() int() float() bool()upper() lower() trim() contains()isEmpty() toJson() split() join()path() field() first() keys() replace()now() today() dateAdd() dateFormat() dateDiff()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.
Reaching into JSON
Section titled “Reaching into JSON”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:
using: - httpoutput: temperature: !numsteps: - $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¤t=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:
- return: temperature: "${= path(weather.body, '$.current.temperature_2m')}" # works whether body is raw text or already parseddata 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.
Escaping
Section titled “Escaping”To write a literal ${foo} without resolution, escape it with a doubled dollar:
with: Template: "$${foo} stays literal"Where expressions are not wrapped
Section titled “Where expressions are not wrapped”Conditions in if: and switch: are plain expressions — they are already in expression context, so they take no ${= … } wrapper:
- if: count > 0 # correct# - if: "${= count > 0}" # wrong — double-evaluatedThe full grammar — operator precedence, every built-in, every diagnostic — lives in the Flow Language Reference.