Skip to main content
ZEN is GoRules’ expression language for transforming data and evaluating conditions. It’s designed to be readable by non-programmers while powerful enough for complex business logic.

Two modes

ZEN operates in two modes depending on context:
ModeUsed inExample
StandardExpression nodes, output columnsprice * quantity * (1 - discount)
Unary testDecision table input columns>= 100, [1..10], 'US', 'CA'

Standard mode

Full expressions that return values.

Literals

// Numbers
42
3.14
-17
1e6

// Strings
"hello world"
'single quotes work too'
`template with ${variables}`

// Booleans
true
false

// Null
null

// Arrays
[1, 2, 3]
["a", "b", "c"]

// Objects
{ name: "John", age: 30 }
{ [dynamicKey]: value }

Operators

Arithmetic

OperatorDescriptionExample
+Addition5 + 38
-Subtraction10 - 46
*Multiplication6 * 742
/Division15 / 35
%Modulo17 % 52
^Power2 ^ 101024

Comparison

OperatorDescriptionExample
==Equalx == 5
!=Not equalx != 0
>Greater thanx > 10
<Less thanx < 100
>=Greater or equalx >= 18
<=Less or equalx <= 65

Logical

OperatorDescriptionExample
andLogical ANDa and b
orLogical ORa or b
notLogical NOTnot a

Ternary

condition ? valueIfTrue : valueIfFalse

score >= 70 ? "pass" : "fail"
age >= 18 ? "adult" : age >= 13 ? "teen" : "child"

Null coalescing

Returns the first non-null value:
user.nickname ?? user.name ?? "Anonymous"

Range check

// Inclusive range
x in [1..10]      // true if 1 <= x <= 10

// Exclusive range
x in (0..100)     // true if 0 < x < 100

// Mixed
x in [0..100)     // true if 0 <= x < 100
x in (0..100]     // true if 0 < x <= 100

// Negation
x not in [1..10]

Property access

// Object properties
customer.name
customer.address.city

// Array indexing
items[0]
items[0].price

// Nested access
order.items[0].product.name

Template strings

`Hello, ${name}!`
`Total: ${sum(items)} items`
`Status: ${approved ? 'Approved' : 'Pending'}`

String slicing

Extract substrings using [start:end] notation:
string[0:5]      // Characters 0-4 (first 5)
string[7:12]     // Characters 7-11
string[7:]       // From index 7 to end
string[:5]       // First 5 characters (0-4)
ExpressionInputResult
string[0:5]"sample_string""sampl"
string[7:]"sample_string""string"
string[:6]"sample_string""sample"

Unary test mode

Shorthand syntax used in decision table input columns when a field name is defined. The value being tested is implicitly available, allowing you to write conditions without repeating the field name. When an input column has no field name, standard expression mode is used instead.

Comparisons

> 100         // Greater than 100
< 50          // Less than 50
>= 18         // Greater or equal to 18
<= 65         // Less or equal to 65
== "active"   // Equal to "active"
!= 0          // Not equal to 0

Ranges

[1..100]      // Between 1 and 100 (inclusive)
(0..100)      // Between 0 and 100 (exclusive)
[18..65)      // 18 to 64
(0..100]      // 1 to 100

Lists

'US', 'CA', 'GB'           // Match any of these strings
1, 2, 3, 5, 8              // Match any of these numbers
"pending", "processing"    // Match any of these

Combined conditions

> 0 and < 100              // Between 0 and 100
>= 18 and <= 65            // Working age
< 0 or > 100               // Outside 0-100

Functions in unary mode

startsWith($, "PRE-")      // String starts with prefix
contains($, "urgent")      // String contains substring
len($) > 5                 // Length greater than 5
The $ symbol represents the value being tested.

Closures and iteration

The # symbol represents the current element when iterating:
map([1, 2, 3], # * 2)                    // [2, 4, 6]
map(items, #.price * #.quantity)         // [totals...]
filter([1, 2, 3, 4, 5], # > 3)           // [4, 5]
some([1, 2, 3], # > 2)                   // true
all([1, 2, 3], # > 0)                    // true

Named aliases

For readability, use as to name the current element:
map(cart.items as item, item.price * item.quantity)
filter(users as user, user.isActive and user.age >= 18)
some(orders as order, order.status == 'pending')
This is equivalent to using # but clearer when expressions are complex.

Assignment

Create values and build objects within expressions.

Basic assignment

a = 5                        // {"a": 5}
name = 'John'                // {"name": "John"}
items = [1, 2, 3]            // {"items": [1, 2, 3]}
config = {debug: true}       // {"config": {"debug": true}}

Property assignment

Assign to nested paths — intermediate objects are created automatically:
user.name = 'Alice'                      // {"user": {"name": "Alice"}}
user.profile.bio = 'Developer'           // {"user": {"profile": {"bio": "Developer"}}}
app.config.database.host = 'localhost'   // Creates full nested structure

Multiple assignments

Separate with semicolons:
a = 1; b = 2                             // {"a": 1, "b": 2}
user.name = 'Charlie'; user.age = 35     // {"user": {"name": "Charlie", "age": 35}}

Assignment with expressions

counter = counter + 1                    // Increment existing value
total = price * quantity                 // Compute from input
fullName = firstName + ' ' + lastName    // String concatenation
doubled = map(numbers, # * 2)            // Array operations
status = score > 70 ? 'pass' : 'fail'    // Conditional

Returning values

The last expression determines the return value:
a = 5; b = 10; a + b                     // Returns 15
user.name = 'Eve'; user.name             // Returns "Eve"
config.debug = true; config              // Returns {"debug": true}
config.debug = true; $root               // Returns {"config": {"debug": true}}
Use $root to return the entire context object.