Expressions
Aceryx uses sandboxed JavaScript expressions for workflow logic, form visibility, and data transformations. All expressions are evaluated in a safe, isolated sandbox powered by goja — a pure Go JavaScript runtime.
Overview
Expressions are used in several places:
- Workflow conditions —
whenclauses on edges (which step executes next) - Step activation conditions — Whether a step should activate based on case data
- Form field visibility — Show/hide fields based on other field values
- Data transformations — Transform step results before merging into case data
- Connector templating — Substitute values in connector configurations
Security & Limits
All expressions run in a sandboxed environment with no access to:
- Filesystem
- Network
- System calls
- External processes
- Host runtime internals
The sandbox is enforced at the language level, not just by restrictions.
Expression limits:
- Maximum size: 4KB
- Timeout: 100ms (configurable)
- Runtimes are pooled for concurrency
Expression Context
Expressions have access to a context object containing:
{
// Case data
case: {
id: "uuid",
data: {
complaintCategory: "billing",
amount: 150.00,
// ... all case data fields
},
status: "active",
createdAt: "2026-04-04T10:00:00Z"
},
// Step results (keyed by step name)
intake: {
result: {
validated: true,
notes: "All fields present"
},
outcome: "completed"
},
review: {
result: {
riskScore: 0.3,
decision: "approved"
},
outcome: "approved"
},
// Current step info (when evaluating a condition)
step: {
name: "approval",
type: "task",
status: "pending"
}
}Examples
Workflow Edge Conditions
Determine which step executes next based on data:
steps:
intake:
type: task
# ...
review:
type: agent
# ...
approval:
type: task
# ...
edges:
# Go to review after intake
- from: intake
to: review
when: "intake.outcome == 'completed'"
# Go to approval if review recommends it
- from: review
to: approval
when: "review.result.decision == 'needs_approval'"
# Otherwise, go to closing (final step)
- from: review
to: closing
when: "review.result.decision != 'needs_approval'"Step Activation Conditions
Conditionally activate a step based on case data:
steps:
expedited_review:
type: agent
condition: "case.data.amount > 10000"
# Only activates if amount exceeds threshold
prompt: |
High-value case detected (${case.data.amount}).
Perform expedited review...
escalation:
type: task
condition: "review.result.riskScore > 0.8"
# Only activates if risk is high
assignTo: "supervisor_role"Form Field Visibility
Show/hide fields in a task form based on other fields:
{
"type": "object",
"properties": {
"complaintCategory": {
"type": "string",
"enum": ["billing", "delivery", "product_quality"]
},
"billingDetails": {
"type": "object",
"properties": {
"invoiceNumber": { "type": "string" },
"overchargedAmount": { "type": "number" }
},
"visible": "complaintCategory == 'billing'"
},
"deliveryDetails": {
"type": "object",
"properties": {
"trackingNumber": { "type": "string" },
"deliveryDate": { "type": "string", "format": "date" }
},
"visible": "complaintCategory == 'delivery'"
}
}
}Data Transformations
Transform step results before merging into case data:
steps:
llm_extraction:
type: agent
prompt: |
Extract structured data from the complaint text.
Return JSON with: category, severity, estimatedResolutionTime
# Result might be a single object or an array
merge_extraction:
type: connector
connector: builtin.merge
config:
source: llm_extraction.result
# Transform: capitalize category
transform: |
{
extractedCategory: source.category.toUpperCase(),
riskLevel: source.severity > 0.7 ? 'high' : 'low',
estimatedHours: source.estimatedResolutionTime
}
# Result is merged into case.dataExpression Language (JavaScript via Goja)
Aceryx uses goja, a pure Go JavaScript runtime with ES5.1+ support. Most modern JavaScript features work, but some are not available:
Supported
- Variables & constants:
const,let,var - Operators:
==,===,!=,!==,<,>,<=,>=,&&,||,!,? :,+,-,*,/,% - Objects:
{ a: 1, b: 2 } - Arrays:
[1, 2, 3], array methods - Strings: String literals, template literals (backticks)
- Functions: Arrow functions, regular functions, closures
- Built-in objects:
Math,Date,JSON,Object,Array,String,Number,RegExp,Error - Comparison: String equality, numeric comparison, loose and strict equality
- Logical operations:
&&,||,!
Not Supported (by design)
- Filesystem:
require('fs'),readFile()— no access - Network:
fetch(),http— no access - External processes:
child_process,exec()— no access - Globals:
process,global,setTimeout,setInterval— no access - Import/require: Cannot load external modules
Common Patterns
Comparison
// Exact match
status == "active"
// Numeric comparison
amount > 1000
amount >= 500 && amount < 2000
// Loose vs strict equality (both work)
outcome == "approved" // Loose
outcome === "approved" // Strict (recommended)
// Boolean check
isUrgent == true
hasDocuments // Shorthand for hasDocuments == true
String Operations
// String contains
category.includes("billing")
description.toLowerCase().startsWith("urgent")
// String length
description.length > 100
// String methods (all available)
text.substring(0, 10)
text.replace("old", "new")
text.split(",")
text.trim()Array Operations
// Array includes
items.includes("priority")
// Array length
attachments.length > 0
// Array methods
ids.map(id => id.toUpperCase())
numbers.filter(n => n > 100)
scores.reduce((sum, score) => sum + score, 0)
// Check if any/all conditions met
items.some(item => item.status == "pending")
items.every(item => item.verified == true)Object Operations
// Property access
case.data.amount
step.result.riskScore
// Property existence
case.data.hasOwnProperty("category")
// Nested access
case.data.customer.location.zip
// Optional chaining (ES2020 - may work)
case.data?.metadata?.priorityMath Operations
amount * 1.1 // Add 10%
Math.round(value)
Math.min(...values)
Math.max(...values)
Math.abs(value)
Math.floor(value)
Math.ceil(value)Ternary Operator
// Choose between two values
case.data.amount > 10000 ? "high" : "low"
// Nested ternary (avoid for readability)
amount < 100 ? "low" : amount < 1000 ? "medium" : "high"Complex Logic
// Multiple conditions
(status == "active" && priority == "high") || isEscalated
// Short-circuit evaluation
outcome == "approved" && approval_date && approval_date > "2026-01-01"
// Function definition (for reuse)
function calculateTotalDays() {
return (new Date(case.data.deadline) - new Date()) / (1000 * 60 * 60 * 24)
}
calculateTotalDays() > 7Type Coercion
JavaScript’s loose typing applies:
// These are all truthy (or truthy-ish)
"string" // Non-empty string
1 // Non-zero number
true
[1, 2, 3] // Non-empty array
{ a: 1 } // Non-empty object
// These are falsy
0 // Zero
"" // Empty string
false
null
undefined
NaNUse strict equality (===) to avoid surprises:
// Loose (can be confusing)
0 == false // true! (type coercion)
"" == 0 // true! (type coercion)
// Strict (safer)
0 === false // false
"" === 0 // false
Error Handling
If an expression has a syntax error or throws an exception, evaluation fails and the step’s condition is evaluated as false (safest behavior for workflow decisions).
Debugging
Expressions are logged in structured logs with context:
{
"time": "2026-04-04T10:00:00Z",
"level": "DEBUG",
"msg": "Expression evaluated",
"expression": "intake.outcome == 'approved'",
"result": true,
"context": {
"caseId": "uuid",
"stepName": "review"
}
}If evaluation fails:
{
"time": "2026-04-04T10:00:00Z",
"level": "WARN",
"msg": "Expression evaluation error",
"expression": "review.result.riskScore > 'invalid'",
"error": "Cannot compare number and string",
"defaultResult": false
}Expression Caching
Compiled expressions are cached to improve performance. The cache is invalidated when:
- A workflow is updated
- A case type schema changes
- Server restarts
Best Practices
- Keep expressions simple — If it gets complex, consider moving logic to an agent step
- Use strict equality —
===instead of==to avoid type coercion surprises - Test expressions — Use the API test endpoint to verify behavior before deployment
- Comment complex logic — Use comments in workflow YAML to explain “why”
- Avoid side effects — Don’t rely on expressions for stateful operations
- Use meaningful names — Name workflow steps clearly so expressions are self-documenting
Example well-written expressions:
edges:
# Simple, clear condition
- from: intake
to: review
when: "intake.outcome == 'valid'"
# Readable multi-condition
- from: review
to: approval
when: "review.result.riskScore > 0.8 && case.data.amount > 10000"
# Ternary for clarity
- from: review
to: closing
when: "review.result.decision == 'approved' ? true : false"Expression API Reference
Context Object
The context is passed to every expression:
case— Current case datacase.id— Case UUIDcase.data— Case data object (validated against schema)case.status— “active”, “closed”, “cancelled”case.createdAt— ISO8601 timestampcase.version— Current version number
step[name]— Result of completed stepsstep[name].result— Step result datastep[name].outcome— Step outcome stringstep[name].error— Error message if step failed
step— Current step (when evaluating activation conditions)step.name— Step identifierstep.type— “task”, “agent”, “connector”step.status— “pending”, “active”, “completed”
Built-in Functions
Date/Time:
addDays(dateString, days)— Add days to a date
String Functions:
lower(string)— Convert to lowercaseupper(string)— Convert to uppercasecontains(array, value)— Check if array contains valuelenOf(value)— Get length of string or array
Global Functions:
JSON.stringify(obj)— Convert object to JSON stringJSON.parse(str)— Parse JSON string to objectMath.round(),Math.floor(),Math.ceil(),Math.min(),Math.max()— Math functionsMath.random()— Generate random number (0-1)
Connector Config Templating
Connector configurations also support Handlebars-style templating for substituting values:
steps:
send_slack:
type: connector
connector: slack
config:
channel: "#urgent"
message: |
New complaint from {{case.data.customer.name}}:
Amount: ${{case.data.amount}}
Category: {{case.data.complaintCategory}}This is different from expressions—it’s simple string substitution, not JavaScript evaluation. Use it for simple value insertion; use expressions for logic.