Search documentation

Search for pages in the documentation

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

cel
// 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

cel
==    // Equal
!=    // Not equal
<     // Less than
<=    // Less than or equal
>     // Greater than
>=    // Greater than or equal

Examples:

cel
json.value == 5
json.score >= 7
json.status != "completed"

Logical Operators

cel
&&    // AND
||    // OR
!     // NOT

Examples:

cel
json.urgent && json.score > 5
json.type == "sales" || json.type == "support"
!json.processed

Arithmetic Operators

cel
+     // Addition
-     // Subtraction
*     // Multiplication
/     // Division
%     // Modulo

Examples:

cel
json.quantity * json.price
json.total / json.count
json.value % 2 == 0  // Is even

Membership Operator

cel
in    // Check if element is in a list

Examples:

cel
"a" in ["a", "b", "c"]            // true
json.type in ["sales", "support"] // Check type

Member Access

cel
// 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.

cel
trigger.eventType         // "MEETING_ENDED"
trigger.meetingId         // Meeting identifier
trigger.organizationId    // Organization ID
trigger.timestamp         // When event occurred

Examples:

cel
// Check event type
trigger.eventType == "MEETING_ENDED"

// Use meeting ID
trigger.meetingId != null

json

Contains output from the upstream node.

cel
// 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:

cel
// 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.

cel
items                     // Full array
items.size()              // Number of items
items[0]                  // First item

Examples:

cel
// Check if any items
items.size() > 0

// Check if within limit
items.size() <= 100

Built-in Functions

String Functions

cel
// 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:

cel
// 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

cel
// 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:

cel
// 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

cel
// 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:

cel
// Check if value is a number
type(json.value) == int

// Convert string to number for comparison
int(json.stringValue) > 5

Time Functions

cel
// Parse timestamp (ISO 8601 format)
timestamp("2024-01-15T10:30:00Z")

Conditional Expressions

cel
// 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:

cel
// 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

cel
// 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

cel
// Range check
json.score >= 1 && json.score <= 10

// Threshold
json.value > 50

// Equality
json.count == 5

String Matching

cel
// 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

cel
// 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

cel
// 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:

cel
json.value >= 7

Check for recording:

cel
json.callRecording != null

Filter by meeting type:

cel
json.meeting.type == "external"

Complex condition:

cel
json.callRecording != null &&
json.meeting.attendees.size() > 1 &&
json.meeting.durationMinutes > 10

Select Many Array Path

Access attendees:

cel
json.meeting.attendees

Access AI list output:

cel
json.value

Filter external attendees:

cel
json.meeting.attendees.filter(a, !a.internal)

Dynamic Values

Item count:

cel
json.items.size()

Conditional value:

cel
json.urgent ? "URGENT" : "NORMAL"

Error Handling

Common Errors

ErrorCauseSolution
"No such field"Property doesn't existCheck property name, use null check
"Type mismatch"Wrong type comparisonCheck types, use conversion
"Division by zero"Divide by 0Add check for divisor
"Index out of bounds"Array index invalidCheck size first

Defensive Patterns

cel
// 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:

MacroDescriptionExample
all(x, condition)True if all elements matchlist.all(x, x > 0)
exists(x, condition)True if any element matcheslist.exists(x, x > 10)
exists_one(x, condition)True if exactly one matcheslist.exists_one(x, x == 5)
map(x, transform)Transform each elementlist.map(x, x * 2)
filter(x, condition)Keep matching elementslist.filter(x, x > 5)

Debugging Tips

  1. Start simple - Build complex expressions incrementally
  2. Check nulls - Always verify objects exist before accessing properties
  3. Verify types - Ensure you're comparing compatible types
  4. Test edge cases - Empty arrays, null values, missing fields
  5. 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 \\