Vex Expression Language Reference

Vex (Vesbite Expressions) is the expression language used throughout Vesbite workflows. It is based on Scriban and uses {{ }} delimiters. Vex expressions can appear in any node input that supports expressions, including text fields, code editors, and condition inputs.

Syntax Overview

Expressions are enclosed in double curly braces:

{{ expression }}

Plain text outside of {{ }} is passed through literally. You can mix text and expressions:

Tag {{ output("trigger").epc }} was read on antenna {{ output("trigger").antenna }}

A field containing only an expression (no surrounding text) preserves the expression’s native type – numbers, booleans, objects, and arrays are not converted to strings.

Data Types

TypeExamplesDescription
string"hello", 'world'Text values. Single or double quotes.
number42, 3.14, -7Integer or decimal numbers.
booleantrue, falseLogical values.
nullnullAbsence of a value.
object{ "key": value }Key-value collections.
array[1, 2, 3]Ordered lists.

Operators

Arithmetic

OperatorDescriptionExampleResult
+Addition{{ 2 + 3 }}5
-Subtraction{{ 10 - 4 }}6
*Multiplication{{ 3 * 7 }}21
/Division{{ 20 / 4 }}5
%Modulo{{ 17 % 5 }}2

Comparison

OperatorDescriptionExampleResult
==Equal{{ 1 == 1 }}true
!=Not equal{{ 1 != 2 }}true
>Greater than{{ 5 > 3 }}true
<Less than{{ 3 < 5 }}true
>=Greater or equal{{ 5 >= 5 }}true
<=Less or equal{{ 3 <= 5 }}true

Logical

OperatorDescriptionExampleResult
&& or andLogical AND{{ true && false }}false
|| or orLogical OR{{ true || false }}true
! or notLogical NOT{{ !true }}false

Variable Access

Workflow Variables

Variables set by Set Variable nodes are accessed through the variables object:

{{ variables.name }}
{{ variables.tagCount }}

For variable names containing hyphens or special characters, use bracket notation:

{{ variables["hyphenated-name"] }}
{{ variables["my variable"] }}

Node Output Access

Access the output of a previous node using the output() function with the node’s activity ID:

{{ output("activityId") }}
{{ output("activityId").property }}
{{ output("activityId").nested.property }}

Alternative syntax using the activities object:

{{ activities.activityId.output }}
{{ activities.activityId.output.property }}
{{ activities["activityId"].output.property }}

The output() function returns the last output produced by the specified activity. If the activity has not executed, it returns null.

Pipe Syntax (Filters)

Filters transform values using the pipe (|) operator:

{{ value | filter_name }}
{{ value | filter_name: arg1, arg2 }}

Filters can be chained:

{{ variables.name | string.upcase | string.truncate 10 }}

String Filters

FilterDescriptionExampleResult
string.upcaseConvert to uppercase{{ "hello" | string.upcase }}HELLO
string.downcaseConvert to lowercase{{ "HELLO" | string.downcase }}hello
string.capitalizeCapitalize first letter{{ "hello world" | string.capitalize }}Hello world
string.stripRemove leading/trailing whitespace{{ " hi " | string.strip }}hi
string.truncateTruncate to length{{ "hello world" | string.truncate 5 }}he...
string.appendAppend text{{ "hello" | string.append " world" }}hello world
string.prependPrepend text{{ "world" | string.prepend "hello " }}hello world
string.replaceReplace occurrences{{ "hello" | string.replace "l" "r" }}herro
string.removeRemove occurrences{{ "hello" | string.remove "l" }}heo
string.splitSplit into array{{ "a,b,c" | string.split "," }}["a","b","c"]
string.sliceExtract substring{{ "hello" | string.slice 1 3 }}ell
string.containsCheck if contains{{ "hello" | string.contains "ell" }}true
string.starts_withCheck prefix{{ "hello" | string.starts_with "hel" }}true
string.ends_withCheck suffix{{ "hello" | string.ends_with "llo" }}true
string.sizeString length{{ "hello" | string.size }}5
string.pad_leftPad on the left{{ "42" | string.pad_left 5 "0" }}00042
string.pad_rightPad on the right{{ "42" | string.pad_right 5 "0" }}42000

Math Filters

FilterDescriptionExampleResult
math.absAbsolute value{{ -5 | math.abs }}5
math.ceilRound up{{ 3.2 | math.ceil }}4
math.floorRound down{{ 3.8 | math.floor }}3
math.roundRound to nearest{{ 3.5 | math.round }}4
math.plusAdd{{ 5 | math.plus 3 }}8
math.minusSubtract{{ 10 | math.minus 3 }}7
math.timesMultiply{{ 4 | math.times 3 }}12
math.divided_byDivide{{ 20 | math.divided_by 4 }}5
math.moduloModulo{{ 17 | math.modulo 5 }}2
math.minMinimum of two values{{ 5 | math.min 3 }}3
math.maxMaximum of two values{{ 5 | math.max 3 }}5

Array Filters

FilterDescriptionExampleResult
array.joinJoin elements{{ [1,2,3] | array.join ", " }}1, 2, 3
array.firstFirst element{{ [1,2,3] | array.first }}1
array.lastLast element{{ [1,2,3] | array.last }}3
array.sizeElement count{{ [1,2,3] | array.size }}3
array.reverseReverse order{{ [1,2,3] | array.reverse }}[3,2,1]
array.sortSort ascending{{ [3,1,2] | array.sort }}[1,2,3]
array.uniqRemove duplicates{{ [1,2,2,3] | array.uniq }}[1,2,3]
array.compactRemove nulls{{ [1,null,3] | array.compact }}[1,3]
array.concatConcatenate arrays{{ [1,2] | array.concat [3,4] }}[1,2,3,4]
array.mapMap property{{ tags | array.map "epc" }}EPCs array
array.containsCheck membership{{ [1,2,3] | array.contains 2 }}true
array.addAdd element{{ [1,2] | array.add 3 }}[1,2,3]
array.remove_atRemove at index{{ [1,2,3] | array.remove_at 1 }}[1,3]
array.insert_atInsert at index{{ [1,3] | array.insert_at 1 2 }}[1,2,3]
array.offsetSkip elements{{ [1,2,3,4] | array.offset 2 }}[3,4]
array.limitTake first N{{ [1,2,3,4] | array.limit 2 }}[1,2]

Date Filters

FilterDescriptionExampleResult
date.nowCurrent UTC time{{ date.now }}2026-03-20T10:30:00Z
date.to_stringFormat a date{{ date.now | date.to_string "%Y-%m-%d" }}2026-03-20
date.add_daysAdd days{{ date.now | date.add_days 7 }}(7 days from now)
date.add_monthsAdd months{{ date.now | date.add_months 1 }}(1 month from now)
date.add_yearsAdd years{{ date.now | date.add_years 1 }}(1 year from now)

Date format specifiers:

SpecifierMeaningExample
%Y4-digit year2026
%m2-digit month03
%d2-digit day20
%HHour (24h)14
%MMinute30
%SSecond00

Object Filters

FilterDescriptionExampleResult
object.keysGet keys{{ obj | object.keys }}Array of key names
object.valuesGet values{{ obj | object.values }}Array of values
object.sizeProperty count{{ obj | object.size }}Number of properties
object.has_keyCheck key exists{{ obj | object.has_key "name" }}true or false
object.typeofGet type name{{ 42 | object.typeof }}number

Control Flow

if / else / end

{{ if variables.rssi > -50 }}
  Strong signal
{{ else }}
  Weak signal
{{ end }}

With else if:

{{ if variables.count > 100 }}
  High volume
{{ else if variables.count > 10 }}
  Normal volume
{{ else }}
  Low volume
{{ end }}

for / end

Iterate over arrays:

{{ for tag in variables.tags }}
  EPC: {{ tag.epc }}, RSSI: {{ tag.rssi }}
{{ end }}

Access the loop index:

{{ for tag in variables.tags }}
  {{ for.index }}: {{ tag.epc }}
{{ end }}

Loop variables:

VariableDescription
for.indexZero-based index
for.index1One-based index
for.firsttrue on first iteration
for.lasttrue on last iteration
for.eventrue on even iterations
for.oddtrue on odd iterations

while / end

{{ while variables.retries > 0 }}
  Retrying...
  {{ variables.retries = variables.retries - 1 }}
{{ end }}

The loop limit is 100,000 iterations to prevent infinite loops.

Common Patterns and Recipes

Access a trigger’s event data

{{ output("trigger").epc }}
{{ output("trigger").rssi }}

Conditional default value

{{ if output("query").rowCount > 0 }}
  {{ output("query").rows | array.first }}
{{ else }}
  No results found
{{ end }}

Build a JSON string dynamically

{"epc": "{{ output("trigger").epc }}", "location": "{{ variables.location }}", "timestamp": "{{ date.now | date.to_string "%Y-%m-%dT%H:%M:%SZ" }}"}

Check if a value exists

{{ if output("redis").exists }}
  Cached: {{ output("redis").value }}
{{ else }}
  Cache miss
{{ end }}

Extract EPCs from a batch tag read

{{ output("trigger").tags | array.map "epc" | array.join ", " }}

Count items matching a condition

{{ for tag in output("trigger").tags }}
  {{ if tag.rssi > -50 }}
    {{ variables.strongCount = (variables.strongCount ?? 0) + 1 }}
  {{ end }}
{{ end }}

String interpolation with device data

Device {{ output("action").deviceId }} reported {{ output("action").success ? "success" : "failure" }}

Escaping

To output literal {{ }} delimiters without evaluation, use the raw block:

{%{  {{ this is not evaluated }}  }%}

Within strings, standard escape sequences apply:

SequenceCharacter
\"Double quote
\\Backslash
\nNewline
\tTab

Error Handling

If an expression fails to parse or evaluate, Vesbite raises an ExpressionEvaluationException with the expression text, language (vex), and error details. Common errors:

ErrorCauseFix
Parse errorInvalid syntax in the expressionCheck for unclosed braces, missing quotes, or invalid operators
Evaluation errorRuntime failure during evaluationVerify that referenced variables and outputs exist
null referenceAccessing a property on nullAdd a null check with if or use the ?? operator

See Also