Help: expressions¶✎ edit
Expressions compute values: in rules, in computed fields (@calc), in object queries (?query=), in ```table fences, and in the Fiddle, where you can try every line below. Each example shows the expression, then its value.
Values¶✎ edit
42→422.5→2.5"hello"→"hello"'single quotes too'→"single quotes too"true→true[1, 2, 3]→[1, 2, 3]{a: 1, b: "x"}→{a: 1, b: "x"}null ?? "none"→"none"
Numbers are Int (whole, 64-bit, wraps like Java's long) or Float. Number accepts either. Text is String. Dates and durations have their own types, below.
Arithmetic¶✎ edit
1 + 2 * 3→7(1 + 2) * 3→999 / 2→49.54 / 2→2.07 % 3→11 + 2.5→3.5
/ always gives a Float. Dividing by zero is an error (E_ARITH), never infinity.
Text¶✎ edit
"n=${1 + 2}"→"n=3""a" + "b"→"ab""ell" in "hello"→truetoUpper("abc")→"ABC"split(s = "a,b,c", by = ",")→["a", "b", "c"]
Write \${ for a literal ${. Triple quotes ("""…""") hold text over several lines.
Comparing and logic¶✎ edit
1 is 1→true1 is not 2→true5 is "5"→false1 < 2 and 3 > 2→truefalse or true→truenot false→true2 in [1, 2]→true3 not in [1, 2]→true"a" in {a: 1}→true
is and == are the same, as are and/&&, or/||, not/!. and and or take Booleans only: for a default value use ??. Comparisons don't chain: 1 < 2 < 3 is an error.
Missing values¶✎ edit
x ?? 7→7x is defined→falsex is empty→true[] is empty→true{a: null}.a ?? "none"→"none"
Reading a name that has no value is an error (E_UNDEFINED) unless you test it or give it a default. ?. reads a field of something that may be null.
Choosing¶✎ edit
if 2 > 1 then "yes" else "no"→"yes"(if true then 1 else 2) + 5→6
An if in an expression always has an else.
Types and casts¶✎ edit
5 is a Int→true5.5 is a Int→false"x" is a String→true"42" as Int→424.9 as Int→412 as String→"12"typeOf(1 + 1)→"Int"
Objects and lists¶✎ edit
{a: 1} + {b: 2}→{a: 1, b: 2}{a: {x: 1}} ++ {a: {y: 2}}→{a: {x: 1, y: 2}}{a: 1, b: 2} - {b: ""}→{a: 1}[1, 2] + 3→[1, 2, 3][1] ++ [2]→[1, 2][1, 2, 3][-1]→3{"a-b": 2}["a-b"]→2{a: 5}.a→5
+ merges objects one level deep (the right side wins); ++ merges all the way down and joins lists. A negative index counts from the end.
Working with lists¶✎ edit
[1, 2, 3] map (x => x * 2)→[2, 4, 6][1, 2, 3] filter (x => x > 1)→[2, 3][1, 2, 3] exists (x => x > 2)→true[[1], [2, 3]] flatMap (x => x)→[1, 2, 3][1, 2, 3] fold (acc = 0) (x => acc + x)→6[1, 2, 3] mkString ", "→"1, 2, 3"[{id: "a"}, {id: "b"}] indexBy "id"→{a: {id: "a"}, b: {id: "b"}}{a: 1} map (p => p.key + p.value)→["a1"]ids flatMap par (i => http.json(url = "https://…/" + i)):flatMap, with the web calls running at once, at most 4 at a time (flatMap par 8 (…)for up to 8, at most 32); results stay in the order ofids.map paris the same formap. If one fails, the others finish first, then the error is raised. With nothing to wait for,parchanges nothing.[1, 2, 3] filter (x => x > 1) map (x => x * 10)→[20, 30]
The part in parentheses is a small function: x => … takes each item as x. On an object, map, filter and exists see {key, value} pairs.
Patterns¶✎ edit
"/acct/42" matches /\/acct\/(?<id>\d+)/→true"ab" ~= /a./→true
A pattern is written between slashes, not quotes. In a rule, named groups like (?<id>…) become variables after a successful match.
Dates and durations¶✎ edit
("2026-09-24" as Date) < ("2026-09-25" as Date)→true("2026-09-24T00:00:00Z" as Date) + "2h30m"→"2026-09-24T02:30:00.000Z"("2026-09-25" as Date) - ("2026-09-24" as Date)→"1d""90s" as Duration→"1m30s"isoDate("2026-09-24T10:00:00Z" as Date)→"2026-09-24"
A duration is text like "5s", "5 seconds", "2h30m", "1d"; next to a date it's read as a duration. Dates are ISO text when stored.
Core functions¶✎ edit
Functions take named arguments, split(s = "a,b", by = ","); one that takes a single argument also takes it bare, sizeOf([1, 2]); a variable with the argument's name can stand for it, split(s, by). f(obj...) passes an object's fields as the arguments.
sizeOf(x): length of a String or list, number of fields of an object.typeOf(x): the type's name.now(): the current date and time.today(): today at 00:00 UTC.isoDate(d): a Date asYYYY-MM-DD.toMillis(x): a Date or Duration as milliseconds.uuid(): a random id.trim(s),toUpper(s),toLower(s).split(s, by): text to a list.replaceAll(s, regex, with),replaceFirst(s, regex, with): the regex given as text.slice(x, from, until): part of a String or list;untiloptional, negatives count from the end.range(from, until): the Ints fromfromup tountil - 1.flatten(xs): one level of nested lists.math.sum(xs),math.min(xs),math.max(xs),math.average(xs): over a list of numbers; average is always a Float.nicej(x): pretty JSON text.hashcode(x): a sha1 of the value.urlencode(s),base64encode(s),base64decode(s).json.parse(text): JSON text to a value.csv.parse(text): CSV with a header row to a list of rows; numeric cells become numbers.http.json(url, headers),http.text(url, headers): fetch a web page (https, public sites, 10 s, 2 MB), in rules and flows only: the flow pauses while it waits, and the server carries on. In a computed field, a table or a query it's an error (E_WAIT): fetch in a rule and store the result.
In rules there are also the engine's own messages: log(msg), diesel.throw(code, msg), dom.upsert(cls, entity), dom.find(cls, key), dom.list(cls), dom.remove(cls, key).
When it goes wrong¶✎ edit
Errors are never silent: they say what went wrong and where. The common ones are E_UNDEFINED (a name with no value: use ?? or is defined), E_TYPE (the wrong kind of value, like "a" or true), E_PARSE (the text isn't an expression), E_ARITH (division by zero), E_ARG (a function's arguments) and E_NO_TOOL (no such function).