FEEL Expression Language Reference
Contents
1. Overview
FEEL (Friendly Enough Expression Language) is the expression language defined in the DMN 1.3 specification. Priostack uses FEEL for:
- DMN input/output cell expressions in decision tables
- BPMN exclusive gateway conditions (
conditionExpression) - BPMN sequence flow conditions
- IoMapping input/output expressions
All FEEL expressions start with
= in Priostack (e.g., = amount > 50000). Omit the = in DMN input cells where the implicit comparison is used.2. Literals and Types
| Type | Example | Notes |
|---|---|---|
| Number | 42, 3.14, -100 | IEEE 754 decimal |
| String | "hello" | Double quotes only |
| Boolean | true, false | Lowercase |
| Null | null | Absence of value |
| Date | date("2026-01-15") | ISO 8601 |
| Time | time("14:30:00") | Local or zoned |
| Date-Time | date and time("2026-01-15T14:30:00Z") | RFC 3339 |
| Duration | duration("P2D"), duration("PT4H30M") | ISO 8601 duration |
| List | [1, 2, 3], ["a","b"] | Zero-indexed |
| Context | {name: "Alice", age: 30} | Like a JSON object |
| Range | [600..750], (0..100) | [ = inclusive, ( = exclusive |
3. Operators
| Operator | Symbol | Example |
|---|---|---|
| Arithmetic | + - * / ** | amount * 0.05 |
| Comparison | = != < <= > >= | score >= 750 |
| Logical AND | and | a > 0 and b < 100 |
| Logical OR | or | role = "admin" or role = "owner" |
| Negation | not() | not(status = "active") |
| In list | in | status in ["pending","review"] |
| In range | in | score in [600..750] |
| Instance of | instance of | x instance of number |
4. Conditionals and Logic
// if-then-else if amount > 50000 then "high-value" else "standard" // Nested if riskLevel = "LOW" then "approved" else if riskLevel = "MEDIUM" and amount < 50000 then "approved" else "rejected" // not() for negation not(verified) // equivalent to verified = false
5. String Functions
| Function | Example | Returns |
|---|---|---|
string length(str) | string length("hello") | 5 |
upper case(str) | upper case("hello") | "HELLO" |
lower case(str) | lower case("HELLO") | "hello" |
substring(str,start,len) | substring("hello",1,3) | "hel" |
string join(list,delim) | string join(["a","b"],",") | "a,b" |
contains(str,sub) | contains("hello","ell") | true |
starts with(str,pre) | starts with("hello","hel") | true |
ends with(str,suf) | ends with("hello","llo") | true |
matches(str,pattern) | matches("abc123","[a-z]+[0-9]+") | true |
replace(str,pattern,replacement) | replace("a-b","\\-","_") | "a_b" |
6. Numeric Functions
| Function | Example | Returns |
|---|---|---|
abs(n) | abs(-5) | 5 |
floor(n) | floor(3.7) | 3 |
ceiling(n) | ceiling(3.2) | 4 |
round up(n,scale) | round up(3.145, 2) | 3.15 |
round down(n,scale) | round down(3.149, 2) | 3.14 |
min(list) | min([1,5,3]) | 1 |
max(list) | max([1,5,3]) | 5 |
sum(list) | sum([1,2,3]) | 6 |
product(list) | product([2,3,4]) | 24 |
mean(list) | mean([1,2,3]) | 2 |
median(list) | median([1,3,5]) | 3 |
7. Date & Time Functions
// Get today's date
today() // date("2026-04-06")
// Get current date-time
now() // date and time("2026-04-06T14:30:00Z")
// Arithmetic
date("2026-04-06") + duration("P7D") // date("2026-04-13")
now() - date and time("2026-04-01T00:00:00Z") // duration since 1 Apr
// Extract parts
year(date("2026-04-06")) // 2026
month(date("2026-04-06")) // 4
day(date("2026-04-06")) // 6
hour(time("14:30:00")) // 14
minute(time("14:30:00")) // 30
// weekday (1=Monday, 7=Sunday)
day of week(date("2026-04-06")) // 1 (Monday)
Common gotcha: Duration comparison —
duration("P2D") < duration("P3D") works, but mixing day-duration and time-duration (e.g., duration("P1D") + duration("PT2H")) returns a mixed duration. Use duration("P1DT2H") directly.8. List Functions
| Function | Example | Returns |
|---|---|---|
count(list) | count([1,2,3]) | 3 |
append(list,item) | append([1,2],3) | [1,2,3] |
concatenate(list...) | concatenate([1],[2,3]) | [1,2,3] |
insert before(list,pos,item) | insert before([1,3],2,2) | [1,2,3] |
remove(list,pos) | remove([1,2,3],2) | [1,3] |
reverse(list) | reverse([1,2,3]) | [3,2,1] |
index of(list,match) | index of([1,2,1],1) | [1,3] |
union(list...) | union([1,2],[2,3]) | [1,2,3] |
distinct values(list) | distinct values([1,2,1]) | [1,2] |
flatten(list) | flatten([[1,2],[3]]) | [1,2,3] |
sort(list, fn) | sort([3,1,2], function(a,b) a < b) | [1,2,3] |
any(list) | any([false,true,false]) | true |
all(list) | all([true,true]) | true |
9. Context (Map) Functions
// Access context property
{name: "Alice", age: 30}.name // "Alice"
// get value
get value({name: "Alice"}, "name") // "Alice"
// get entries
get entries({a: 1, b: 2}) // [{key:"a",value:1},{key:"b",value:2}]
// context merge
context merge({a:1},{b:2}) // {a:1, b:2}
10. Usage in DMN Decision Tables
In decision table input cells, FEEL expressions are used as unary tests against the input column value:
| Input cell | Matches when input value… |
|---|---|
"LOW" | equals the string "LOW" |
< 600 | is less than 600 |
[600..750] | is between 600 and 750 (inclusive) |
"LOW","MEDIUM" | is either "LOW" or "MEDIUM" |
not("HIGH") | is anything except "HIGH" |
| (empty) | matches any value (wildcard) |
Output cells contain full FEEL expressions:
// Output: fixed string "approved" // Output: conditional based on other variables if amount > 100000 then "escalate" else "approve" // Output: computed value amount * interestRate
11. Usage in BPMN Gateways
In BPMN sequence flows from exclusive gateways, add a <conditionExpression>:
<sequenceFlow id="to-approve" sourceRef="gw1" targetRef="approve-task"> <conditionExpression>= riskLevel = "LOW" or (riskLevel = "MEDIUM" and amount < 50000)</conditionExpression> </sequenceFlow>
Tip: Always prefix BPMN conditions with
= to tell the engine this is a FEEL expression. Without it, the engine treats it as a literal string comparison.