Skip to content
npm

Function reference

All functions available in expressions, grouped by purpose. Your project may add custom functions on top; check the expression editor's help panel for the live list.

Emptiness & text

FunctionReturnsExample
isEmpty(value)true if empty (null, "", [])isEmpty({companyCode})
notEmpty(value)true if not emptynotEmpty({email})
len(value)length of text or arraylen({items}) >= 3
startsWithAny(text, prefix)true if text starts with prefixstartsWithAny({iban}, "LT")
endsWithAny(text, suffix)true if text ends with suffixendsWithAny({code}, "99")

Collections

FunctionReturnsExample
contains(collection, value)true if array/text contains valuecontains({roles}, "admin")
includesAny(collection, value)alias of containsincludesAny({tags}, "vip")
containsAny(collection, values)true if any of the values presentcontainsAny({roles}, ["admin","editor"])
containsAll(collection, values)true if all values presentcontainsAll({perms}, ["read","write"])
inArray(value, collection)true if value is in collectioninArray({el2}, {el1})

Numbers

FunctionReturnsExample
toNumber(value)value as number (invalid → 0)toNumber({codeFromApi}) > 10
inRange(value, min, max)true if min ≤ value ≤ maxinRange({age}, 18, 65)

When toNumber is actually needed

A Number field already stores a number, so {price} * {quantity} is correct as written and wrapping both sides adds nothing. Use toNumber when the value really arrives as text: a Text field, a value from a data source, a select whose options carry string values, or a Number field switched to Store value as: string.

It has a second, deliberate use. toNumber turns anything invalid into 0, so an empty field becomes zero instead of leaving the whole calculation empty. If that is the behaviour you want, keep it, and know that you kept it for the default and not for the conversion.

Array aggregation

For dynamic panel / dynamic table values (arrays of objects). selector is the field name inside each entry.

FunctionReturnsExample
sumInArray(source, selector)sumsumInArray({orderLines}, "amount")
avgInArray(source, selector)averageavgInArray({grades}, "score")
minInArray(source, selector)minimumminInArray({offers}, "price")
maxInArray(source, selector)maximummaxInArray({offers}, "price")
countInArray(source, selector)countcountInArray({employees}, "id")
firstInArray(source, selector)first valuefirstInArray({history}, "status")
lastInArray(source, selector)last valuelastInArray({history}, "status")
joinInArray(source, selector, separator)joined textjoinInArray({tags}, "name", ", ")
collectValuesFrom(source, selector, unique)array of valuescollectValuesFrom({orders}, "id", true)

Searching and filtering arrays

The functions above work on a field name. These ones take a condition instead, written the same way you write any other expression, and evaluate it once per entry.

FunctionReturnsExample
filterArray(source, condition)the matching entriesfilterArray({users}, role == "ADMIN")
findInArray(source, condition)the first matching entry, or nothingfindInArray({products}, id == {selectedId})
existsInArray(source, condition)true if at least one entry matchesexistsInArray({items}, status == "ACTIVE")
countInArray(source, condition)how many entries matchcountInArray({tasks}, status == "OPEN")
getFirst(source, condition?)first entry, optionally the first matchgetFirst({tasks}, status == "OPEN")
getLast(source, condition?)last entry, optionally the last matchgetLast({history})
sumArray(source, selector)sum of a fieldsumArray({orderItems}, price)
avgArray(source, selector)average of a fieldavgArray({grades}, score)
minArray(source, selector?)smallest number over the entriesminArray({orderItems}, price)
maxArray(source, selector?)largest number over the entriesmaxArray({orderItems}, price)
countArray(source, condition?)how many entries match, or the length without a conditioncountArray({tasks}, status == "OPEN")
joinArray(source, selector?, separator?)the projected values as textjoinArray({users}, name, ", ")
mapArray(source, selector)one value per entrymapArray({users}, name)

Inside a condition you write the entry's own field names directly, with no braces. Everything else in the view stays visible, so you can compare against another element or a variable:

text
countInArray({tasks}, status == {statusFilter})
filterArray({orders}, status == {el5} && total > {minTotal})
filterArray({users}, role == {__variables.requiredRole})

Calls nest, which is how you go from a filtered set to a single text:

text
joinInArray(mapArray(filterArray({tasks}, status == "OPEN"), title), "", ", ")

countInArray also still accepts a plain field name, so older views keep working. Anything containing a comparison is treated as a condition.

Dates

FunctionReturnsExample
today()today, YYYY-MM-DDDefault value: =today()
now()current date-time (ISO)timestamp fields
date(value, mode)normalised date (iso, datetime, timestamp)date({created}, "iso")
day(value) / month(value) / year(value)date partsyear({birthDate}) < 2000
weekDay(value, locale, style)weekday nameweekDay({date}, "en", "long")
weekDayIndex(value)Monday=1 … Sunday=7weekDayIndex({date}) <= 5
isWeekend(value)true on Sat/SunvisibleIf: isWeekend({deliveryDate})
addDays(value, days)date + n daysaddDays(today(), 14)
dateDiffDays(from, to)day differencedateDiffDays({start}, {end}) >= 1
startOfWeek(value) / endOfWeek(value)week boundariesreport period defaults

Data & element control

Advanced. These reach outside the current field:

FunctionDoesExample
getVal(path)reads any value by data pathgetVal("addresses[0].city")
setElementProperty(name, key, value)sets another element's propertysetElementProperty("step2", "disabled", true)
getElementProperty(name, key)reads another element's propertygetElementProperty("sel1", "label")
getProp(name, key)alias of getElementPropertygetProp({sel1}, "placeholder")
setProp(name, key, value)alias of setElementPropertysetProp({el1}, "label", "New label")
runDataSource(name)reloads a data source, returns its resultrunDataSource("loadUsers")
reloadDataSource(name)alias of runDataSource
dataSourceValue(name, contextElement?)the payload a data source loaded last, without waiting for a requestlen(dataSourceValue("loadUsers")) > 0

getVal also answers for a field the user has not touched yet, reading the value straight off the element, so {price} * 2 no longer collapses the moment one side is still empty.

getElementProperty reaches any configured property, not just the value, and nested keys work:

text
getProp("sel1", "placeholder")
getProp("sel1", "options[0].label")
getProp("orders", "dataSource.name")

Writing values back

Everything above reads. These write, so a total or a collected list can be parked in a variable or in another field without leaving the expression language:

FunctionDoesExample
setValue(target, value)writes the value and returns itsetValue({variable1}, sumArray({el1}, el5[].column3))
setVar(name, value)same, target always read as a variable namesetVar({variable1}, {el2} + {el3})
sumValue(target, value)adds a number to what is already there, returns the new totalsumValue({variable1}, {row.column3})
sumVar(name, value)same, for variablessumVar({variable1}, {row.column3} + 40)
addValue(target, value)alias of sumValueaddValue({total}, {row.column3})
pushValue(target, value)appends to the target's array, returns itpushValue({variable1}, {row})
pushVar(name, value)same, for variablespushVar({selected}, {el2})
flattenArray(source)flattens nested arrays into one levelflattenArray(collectValuesFrom({el1}, el5[]))

The target names a variable or an element, it is not replaced by its current value. If the name matches a declared variable the value goes to the variable, otherwise to that element's data path.

sumValue and pushValue change the result every time they run, so use them from an action, not from a condition that recalculates on its own. setValue is safe anywhere: writing the value that is already there does nothing, which is what keeps an expression from looping on the change it caused itself.

An array argument is summed before it is added, so a whole column lands in one call:

text
sumValue({variable1}, collectValuesFrom({el1}, el5[].column3))

Assignment shorthand

The same two writers have a shorter form. Put the target on the left:

text
{variable1} = {row.column3} + 40
{variable1} = ({variable1} + ({row.column3} + 40))
{variable1} += {row.column3}

= becomes setValue, += becomes sumValue. Only the leftmost token is the target; on the right side {variable1} reads normally, which is how the second line adds to itself. Comparisons are untouched, {el2} == 5 stays a condition.

Totals across a dynamic panel

A table inside a dynamic panel exists once per panel entry, so its rows sit deeper in the data. The [] selector walks through every level:

text
{variable1} = sumArray({el1}, el5[].column3)
{variable1} = flattenArray(collectValuesFrom({el1}, el5[]))

The first sums one column across all panels, the second gathers every row of every table into one list.

Labels of choice elements

A Select, Radio, or Checkbox group stores a code and shows a label. Only the element knows the pairing, so these two functions ask it directly:

FunctionReturnsExample
getValue(element)the stored valuegetValue({status}) gives OPEN
getLabel(element, value?)the displayed labelgetLabel({status}) gives Open issue

Name the element either way you prefer, getLabel({status}) or getLabel("status"). The token is treated as the element's name here, not as its value. Pass a second argument to translate some other value through the same option list: getLabel("status", "CLOSED"). For a multi select you get one label per selected value.

Translating dynamic values

Values coming from an API never pass through the Translations tab, because that tab only knows the texts written into the view. translate() opens the same per-language dictionary to any key:

FunctionReturnsExample
translate(value, fallback?)the translated texttranslate({row.status})
t(value, fallback?)alias of translatet({row.status}, "Unknown")
currentLanguage()active language codecurrentLanguage() == "en"

See Translations for where the keys live.

Debugging

FunctionDoesExample
dbg(value, label?)logs to console, returns the valuedbg({el2.selectedStep}, "step")
clog(value, label?)alias of dbgclog({price}, "price")