Skip to main content

Expressions (expr)

Expressions live inside {{ ... }} in any string-typed activity input. The runner evaluates them with expr-lang, which provides a full expression language — not just variable substitution. Most list filtering, dedup, pattern matching, and string munging that authors reach for RunPython for can be done as a single expression.

Syntax shapes

WriteLine(response) # bare variable — passes reference
WriteLine("{{ response.body }}") # property access
WriteLine("{{ items | filter(.active) }}") # full expression
WriteLine("Hi {{ user.name }}, {{ count }} items") # embedded in a string

Rule of thumb: if you're accessing a property, calling a function, doing arithmetic, or piping — wrap it in {{ }}. Bare names without braces only work for single-variable references.

Collection operations

ExpressionResult
filter(items, .active)items where predicate is true
map(items, .name)list of field values
sortBy(items, .priority)sorted ascending
sortBy(items, .priority, "desc")sorted descending
groupBy(items, .status)map[key] → []item
find(items, .id == 42)first match or nil
any(items, .total > 100)true if any element matches
all(items, .priority > 0)true if every element matches
count(items, .active)count of matches
take(items, 5)first N elements
reduce(items, #acc + .amount, 0)fold
reverse(items)reversed copy

Inside predicates, .fieldName is the current element; # is the element itself (useful for scalars — filter(codes, # startsWith "A")).

Pipes

items | filter(.active) | sortBy(.priority) | map(.name) | take(3)
items | sortBy(.updated_at) | last()
names | filter(# endsWith ".pdf") | strings.Join(",")

Pipe chains read left-to-right; each stage's output feeds the next.

Operators

OperatorExampleNotes
in"abc" in namesmembership
containss contains "foo"substring
startsWith / endsWithpath endsWith ".pdf"
matchess matches "[a-z]+"regex
??x ?? "default"nil-coalesce
?.user?.emailsafe navigation
..1..5range → array

Data / type helpers

ExpressionResult
len(items)length
items[0], items[-1]indexing, negative from end
items[1:3]slicing
keys(obj) / values(obj)object keys/values
toPairs(obj)[[key, value], ...]
fromPairs([[k, v], ...])pairs → object
fromJSON(str) / toJSON(value)JSON parse / stringify
date("2026-04-01")time.Time, sortable
now()current time.Time
{"a": 1, "b": 2}object literal (top-level only)
let x = expr; bodylocal binding

Registered function packages

strings.* (38 functions)

ToLower, ToUpper, Contains, HasPrefix, HasSuffix, Split, Join, Replace, ReplaceAll, Trim, TrimPrefix, TrimSuffix, Repeat, Count, Fields, Cut, CutPrefix, CutSuffix, EqualFold, and more. Match Go's strings package naming.

Example: {{ strings.Join(map(codes, # + ".pdf"), ",") }}

math.* (~30 functions)

Abs, Max, Min, Round, Floor, Ceil, Pow, Sqrt, Log, Log10, Log2, trig functions, Pi, E, IsNaN, IsInf.

Example: {{ math.Round(total * 1.2) }}

fuzzy.* (7 functions)

Similarity, IsMatch, Contains, BestMatch, BestMatchWithScore, FindAllMatches, LookupMatch — for approximate string matching.

Example: {{ fuzzy.BestMatch(query, knownCompanies) }}

collections.* (3 functions)

FunctionPurpose
collections.Fnmatch(name, pattern)shell-glob match (*, ?, [...])
collections.Unique(arr)first-seen dedup, any element type
collections.DistinctBy(arr, "field")first-seen dedup by field name

path.* (6 functions)

GetFileName, GetExtension, GetDirectoryName, GetFileNameWithoutExtension, Combine, GetTempPath. They use the robot's own separator, so path.Combine("a", "b") is a\b on Windows and a/b elsewhere — build paths with these rather than by concatenating strings.

Recipes

Each recipe names a task, the before-Python approach, and the expression replacement. Every expression here is covered by a passing probe in expression_capabilities_test.go.

Pick the latest record per unique key

Before — Python with dict grouping + sort.

{{ resources | sortBy(.updated_at, "desc") | collections.DistinctBy("name") }}

Deduplicate a list

{{ collections.Unique(codes) }}
{{ collections.DistinctBy(orders, "customer_id") }}

Set difference (what's expected but missing)

{{ filter(expected, !(# in map(available, .name))) }}

Build a CSV string for a names= filter

{{ strings.Join(map(codes, # + ".pdf"), ",") }}

Match a supplier code against a mapping rule table

{{ let c = supplierCode;
let hit = find(sortBy(mappings, .priority), collections.Fnmatch(c, .pattern));
hit == nil ? c + ".pdf" : hit.tech_sheet + ".pdf" }}

Count duplicates by field value

{{ count(toPairs(groupBy(resources, .name)), let p = #; len(p[1]) > 1) }}

Conditional routing in an IfCondition

{{ any(orders, .status == "pending" && .total > 1000) }}
{{ len(codes) - count(codes, !(# + ".pdf" in map(available, .name))) }}

Safe access into nested webhook payloads

{{ webhookBody?.value?[0]?.resourceData?.id ?? "unknown" }}

First element matching a pattern

{{ find(items, .name matches "^invoice_[0-9]+\\.pdf$") }}

Sort & take the top N

{{ items | sortBy(.score, "desc") | take(5) | map(.id) }}

Known limitations (document, don't try)

These hit expr-lang grammar issues, not things we haven't registered. Workarounds noted.

  • Dict literals inside map()/filter()/count() predicates fail map(codes, {code: #, file: # + ".pdf"}) → parse error. Workaround: map(codes, fromPairs([["code", #], ["file", # + ".pdf"]])), or compute in an outer let binding.
  • .[index] inside a predicate fails count(pairs, len(.[1]) > 1) → parse error. Workaround: rebind with let p = #; p[1].
  • Array + concat and string * repeat are not supported Use strings.Repeat("-", 5); for array concat, use reduce or compute in separate steps.

When RunPython is still the right choice

Use RunPython when you need:

  • Multi-page / spatial PDF text extraction (use span geometry, OCR coords)
  • Stateful loops that mutate across iterations (expressions are pure)
  • External library calls (pandas, numpy, pillow, pymupdf, etc.)
  • Custom regex with capture groups (matches returns bool only)
  • Anything you'd describe as "a small program" rather than "a transform"

Everything else — filtering, dedup, sort, group, set ops, pattern matching, string manipulation — is an expression.