Variables Reference
Complete reference for variable syntax, scope, and resolution in apptor flow workflows.
Two Syntaxes — Never Mix Them
There are exactly two variable syntaxes in apptor flow. Using the wrong one is the most common mistake.
| Context | Syntax | Resolved By | Example |
|---|---|---|---|
| Node property values (prompts, bodies, addresses, etc.) | {varName} | TemplateVariableResolver | Hello {customerName} |
| JUEL condition expressions (If/Else, Loop conditions) | ${expression} | JUEL engine | ${total > 100} |
{varName} is for data substitution. ${expression} is for boolean conditions.
They are completely separate systems and are never interchangeable.
{varName} — Template Variables
Basic Usage
Hello {customerName}, your order #{orderId} is ready.
The TemplateVariableResolver replaces {varName} with the current value of that variable at execution time.
Nested Object Access
{order.id} → order['id']
{order.customer.email} → order['customer']['email']
{items[0].name} → items[0]['name']
Dot notation is supported for any depth. Array index access uses [n].
Environment / Secret Variables
{env.OPENAI_API_KEY} → resolved from secrets store
{env.SUPPORT_EMAIL} → resolved from system variables
{env.VAR_NAME} reads from the organization's secrets store and system variables. These values are never logged.
Null / Missing Variables
If a variable is not found in the execution context:
- The template resolver leaves the placeholder in place:
Hello {unknownVar}→Hello {unknownVar} - No error is thrown — this is a silent substitution failure
- Use If/Else with
${empty varName}to guard against missing variables
Where {varName} Works
- AI Task:
systemPrompt,prompt - Service Task:
endpointPath,body - Email:
recipientAddress,subject,body - SMS:
toNumber,message - Voice Task:
toPhoneNumber,greeting,systemPrompt - Set Variable: value fields
- Output Node:
prompt,title - Input Node:
formTitle - Script Task: variables are injected directly as named constants
- Loop Node:
loopExpressionvalues (the{}part, not the${}wrapper)
${expression} — JUEL Expressions
JUEL (Java Unified Expression Language) is used exclusively for condition evaluation — specifically:
- If/Else node outgoing link conditions
- Loop Node
loopExpression
Syntax
${variableName == 'value'}
${numericVar > 100}
${booleanVar == 'true'}
${!empty someVar}
${var1 == 'x' && var2 == 'y'}
The entire expression must be wrapped in ${...}.
Operators
| Operator | Example | Notes |
|---|---|---|
== | ${status == 'active'} | Equality (use single quotes for strings) |
!= | ${type != 'billing'} | Inequality |
>, < | ${total > 500} | Numeric comparison |
>=, <= | ${count <= 10} | Numeric comparison |
&& | ${a == 'x' && b == 'y'} | Logical AND |
|| | ${a == 'x' || b == 'y'} | Logical OR |
! | ${!active} | Negation |
empty | ${empty customerEmail} | True if null, empty string, or empty collection |
Type Coercion
All workflow variables are stored as strings internally. JUEL handles type coercion:
${total > 100} → coerces 'total' to number if it looks like one
${flag == 'true'} → string comparison — CORRECT for boolean flags
${flag == true} → DO NOT USE — type mismatch, may behave unexpectedly
Always compare boolean-like variables as strings: ${flag == 'true'} not ${flag == true}.
String Functions (EL)
${name.startsWith('J')}
${email.contains('@')}
${status.toLowerCase() == 'active'}
${name.length() > 3}
${name.trim().isEmpty()}
Variable Scope
Variables accumulate and persist across the entire workflow execution:
[Start: {customerName: 'Jane', orderId: 'ORD-123'}]
↓
[AI Task] → sets: {classification: 'billing'}
↓
[Service Task] → sets: {serviceTaskResponse: {...}, serviceTaskStatusCode: 200}
↓
[Script Task] → sets: {formattedTotal: '$99.00'}
↓
[End]
At End Event, the process instance data contains:
{
"customerName": "Jane",
"orderId": "ORD-123",
"classification": "billing",
"serviceTaskResponse": {...},
"serviceTaskStatusCode": 200,
"formattedTotal": "$99.00"
}
Variable Overwriting
Later nodes can overwrite variables set by earlier nodes. This is intentional and useful for updating values. Use unique variable names if you need to preserve intermediate values.
Special System Variables
The engine sets these automatically during execution:
| Variable | Description |
|---|---|
{_error} | Error message from the most recent failed node |
{_errorCode} | Error code from the most recent failure |
{loopIndex} | Zero-based index in current loop iteration |
{loopCount} | Total completed iterations in current loop |
Passing Variables Between Nodes
Service Task → Downstream
[Service Task: GET /customers/{customerId}]
outputs: {serviceTaskResponse}, {serviceTaskStatusCode}
[Script Task: extract fields]
script: return { customerName: serviceTaskResponse.name, customerEmail: serviceTaskResponse.email }
[Email: send email]
recipientAddress: {customerEmail}
subject: Hello {customerName}
AI Task → Downstream (Structured JSON)
[AI Task: classify ticket]
prompt: "Return JSON: {\"classification\": \"...\", \"priority\": \"...\"}"
outputs: {aiResponse} (raw), also sets {classification}, {priority} if JSON parsed
[If/Else]
trueFlow: ${classification == 'billing'}
Script Task → Downstream
// script:
return {
totalFormatted: `$${parseFloat(total).toFixed(2)}`,
isHighValue: parseFloat(total) > 1000
};
[Downstream If/Else]
trueFlow: ${isHighValue == 'true'}
Common Mistakes
Wrong: Using ${} in Property Fields
WRONG: systemPrompt: "Hello ${customerName}" ← JUEL syntax in template context
RIGHT: systemPrompt: "Hello {customerName}"
Wrong: Forgetting Single Quotes in JUEL
WRONG: ${classification == billing} ← unquoted string
RIGHT: ${classification == 'billing'}
Wrong: Comparing Booleans Without Quotes
WRONG: ${isApproved == true}
RIGHT: ${isApproved == 'true'}
Wrong: Not Checking for Null
WRONG: ${customerEmail == 'jane@example.com'} ← throws if customerEmail is null
RIGHT: ${!empty customerEmail && customerEmail == 'jane@example.com'}