CEL Expression Reference
Complete reference for Common Expression Language expressions
Common Expression Language (CEL) is used throughout Agents for conditions, dynamic values, and data manipulation. This implementation uses cel-js, a high-performance JavaScript CEL implementation.
Basic Syntax
Literals
// Strings
"hello world"
'single quotes also work'
r"raw string without escapes"
"""multi-line
string"""
// Numbers (integers and decimals)
42
3.14
-17
// Booleans
true
false
// Null
null
Operators
Comparison Operators
== // Equal
!= // Not equal
< // Less than
<= // Less than or equal
> // Greater than
>= // Greater than or equal
Examples:
json.value == 5
json.score >= 7
json.status != "completed"
Logical Operators
&& // AND
|| // OR
! // NOT
Examples:
json.urgent && json.score > 5
json.type == "sales" || json.type == "support"
!json.processed
Arithmetic Operators
+ // Addition
- // Subtraction
* // Multiplication
/ // Division
% // Modulo
Examples:
json.quantity * json.price
json.total / json.count
json.value % 2 == 0 // Is even
Membership Operator
in // Check if element is in a list
Examples:
"a" in ["a", "b", "c"] // true
json.type in ["sales", "support"] // Check type
Member Access
// Object property access
json.meeting
json.meeting.title
json.meeting.attendees
// Array index access
json.items[0]
json.attendees[0].name
Note: Optional chaining (
?.) is not supported. Always use null checks before accessing nested properties.
Available Variables
trigger
Contains data from the event that triggered the workflow.
trigger.eventType // "MEETING_ENDED"
trigger.meetingId // Meeting identifier
trigger.organizationId // Organization ID
trigger.timestamp // When event occurred
Examples:
// Check event type
trigger.eventType == "MEETING_ENDED"
// Use meeting ID
trigger.meetingId != null
json
Contains output from the upstream node.
// After Load Meeting node
json.meeting // Meeting object
json.meeting.title // Meeting title
json.meeting.attendees // Attendees array
json.callRecording // Recording data
// After AI Prompt node
json.value // AI output (string, int, etc.)
// After any processing node
json // Full upstream output
Examples:
// Check meeting has recording
json.callRecording != null
// Check AI sentiment score
json.value >= 7
// Check attendee count
json.meeting.attendees.size() > 2
items
In batch mode, contains the array of items being processed.
items // Full array
items.size() // Number of items
items[0] // First item
Examples:
// Check if any items
items.size() > 0
// Check if within limit
items.size() <= 100
Built-in Functions
String Functions
// Size (length)
"hello".size() // 5
json.meeting.title.size() // Length of title
// Contains
"hello world".contains("world") // true
json.meeting.title.contains("Q4") // Check title
// StartsWith / EndsWith
"hello".startsWith("he") // true
"hello".endsWith("lo") // true
// Matches (regex)
"test@email.com".matches("[a-z]+@[a-z]+\\.[a-z]+") // true
// indexOf / lastIndexOf
"hello".indexOf("l") // 2
"hello".indexOf("l", 3) // 3 (with offset)
"hello".lastIndexOf("l") // 3
// Substring
"hello".substring(1) // "ello"
"hello".substring(1, 4) // "ell"
Examples:
// Title contains keyword
json.meeting.title.contains("urgent")
// Email domain check
json.email.endsWith("@company.com")
// Not empty
json.notes.size() > 0
// Regex pattern match
json.email.matches(".*@company\\.com$")
List Functions
// Size
[1, 2, 3].size() // 3
json.attendees.size() // Attendee count
// Index access
list[0] // First element
list[list.size() - 1] // Last element
// Membership
"a" in ["a", "b", "c"] // true
json.type in ["sales", "support"] // Check type
// All (macro) - check if all elements match
list.all(x, x > 0) // All positive
// Exists (macro) - check if any element matches
list.exists(x, x > 10) // Any > 10
// Exists One (macro) - exactly one matches
list.exists_one(x, x > 10) // Exactly one > 10
// Map (macro) - transform elements
list.map(x, x * 2) // Double each element
// Filter (macro) - filter elements
list.filter(x, x > 5) // Keep items > 5
Examples:
// Has at least 2 attendees
json.meeting.attendees.size() >= 2
// Type is one of allowed values
json.meetingType in ["discovery", "demo", "negotiation"]
// Any external attendee
json.meeting.attendees.exists(a, !a.internal)
// All attendees have email
json.meeting.attendees.all(a, a.email != null)
Type Functions
// Type checking
type(value) // Returns type
type("hello") == string // true
type(42) == int // true
// Type conversion
int("42") // 42
double("3.14") // 3.14
string(42) // "42"
dyn(value) // Convert to dynamic type
Examples:
// Check if value is a number
type(json.value) == int
// Convert string to number for comparison
int(json.stringValue) > 5
Time Functions
// Parse timestamp (ISO 8601 format)
timestamp("2024-01-15T10:30:00Z")
Conditional Expressions
// Ternary operator
condition ? value_if_true : value_if_false
// Examples
json.urgent ? "HIGH" : "NORMAL"
json.score > 7 ? "positive" : json.score > 3 ? "neutral" : "negative"
Common Patterns
Null Checking
Since optional chaining is not supported, always check for null before accessing nested properties:
// Check for null
json.callRecording != null
// Safe navigation with null check
json.callRecording != null && json.callRecording.transcriptSummary != null
// Multiple levels
json.meeting != null && json.meeting.attendees != null && json.meeting.attendees.size() > 0
Boolean Conditions
// Simple boolean
json.urgent == true
json.processed
// Negation
!json.processed
json.urgent != true
// Combined conditions
json.urgent && !json.processed
(json.score > 7 || json.priority == "high") && !json.completed
Numeric Comparisons
// Range check
json.score >= 1 && json.score <= 10
// Threshold
json.value > 50
// Equality
json.count == 5
String Matching
// Exact match
json.status == "completed"
// Contains
json.title.contains("urgent")
// Multiple values
json.type == "sales" || json.type == "support"
json.type in ["sales", "support", "success"]
// Regex pattern
json.email.matches(".*@company\\.com$")
// Not empty
json.notes != "" && json.notes != null
List Operations
// Has items
json.items.size() > 0
// Specific count
json.attendees.size() == 5
// Check membership
json.category in json.allowedCategories
// Any match
json.tags.exists(t, t == "important")
// All match
json.items.all(item, item.status == "complete")
// Filter and check
json.attendees.filter(a, !a.internal).size() > 0
Date/Time
// Compare timestamps as strings
json.createdAt > "2024-01-01T00:00:00Z"
// Check if set
json.completedAt != null
// Parse and compare (if needed)
timestamp(json.createdAt) > timestamp("2024-01-01T00:00:00Z")
Use Cases
If Node Conditions
Route by sentiment score:
json.value >= 7
Check for recording:
json.callRecording != null
Filter by meeting type:
json.meeting.type == "external"
Complex condition:
json.callRecording != null &&
json.meeting.attendees.size() > 1 &&
json.meeting.durationMinutes > 10
Select Many Array Path
Access attendees:
json.meeting.attendees
Access AI list output:
json.value
Filter external attendees:
json.meeting.attendees.filter(a, !a.internal)
Dynamic Values
Item count:
json.items.size()
Conditional value:
json.urgent ? "URGENT" : "NORMAL"
Error Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
| "No such field" | Property doesn't exist | Check property name, use null check |
| "Type mismatch" | Wrong type comparison | Check types, use conversion |
| "Division by zero" | Divide by 0 | Add check for divisor |
| "Index out of bounds" | Array index invalid | Check size first |
Defensive Patterns
// Check before access
json.callRecording != null && json.callRecording.transcriptSummary != null
// Check size before index
json.items.size() > 0 && json.items[0] != null
// Provide defaults using ternary
json.count != null ? json.count : 0
CEL Macros
The following macros are available for working with lists:
| Macro | Description | Example |
|---|---|---|
all(x, condition) | True if all elements match | list.all(x, x > 0) |
exists(x, condition) | True if any element matches | list.exists(x, x > 10) |
exists_one(x, condition) | True if exactly one matches | list.exists_one(x, x == 5) |
map(x, transform) | Transform each element | list.map(x, x * 2) |
filter(x, condition) | Keep matching elements | list.filter(x, x > 5) |
Debugging Tips
- Start simple - Build complex expressions incrementally
- Check nulls - Always verify objects exist before accessing properties
- Verify types - Ensure you're comparing compatible types
- Test edge cases - Empty arrays, null values, missing fields
- Use parentheses - Clarify operator precedence in complex expressions
Limitations
- No optional chaining: Use explicit null checks instead of
?. - No string case conversion: Use string comparison with expected casing
- Escape regex: Remember to escape special regex characters with
\\