Complex Use Cases
Real-world patterns: directive chaining, multi-expression templates, conditional logic, and cross-node data flows.
1. Dynamic HTTP Body Built from Multiple Directives
An HTTP Request node's body field uses multiple expressions assembled into a JSON payload.
"tenantId": "{@ $ctx.tenant.id }",
"userId": "{@ $ctx.user.id }",
"amount": {@ $var.invoiceTotal },
"currency": "{@ @company.defaultCurrency }",
"referenceId": "{@ $exec.executionId }"
}
All five expressions are evaluated in parallel (parallel interpolation). The assembled JSON is built in a single pass.
2. Conditional Value via JavaScript
When the value depends on a business rule that's too complex for static paths:
3. Cross-Node Data Aggregation
Downstream nodes reference output from multiple upstream nodes:
4. Canned Expression Resolving to Another Expression
Canned expressions can reference other directives. The chain is depth-tracked:
// DB: Name="company.billingEmail", Expression="{@ $ctx.tenant.billingEmail }"
// DB: Name="report.recipient", Expression="@company.billingEmail"
// Expression in node field:
"@report.recipient"
// Chain:
// Depth 0: @report.recipient → resolves alias → "@company.billingEmail"
// Depth 1: @company.billingEmail → resolves alias → "{@ $ctx.tenant.billingEmail }"
// Depth 2: {@ $ctx.tenant.billingEmail } → resolves via ContextDirective → "billing@acme.com"
// Final result: "billing@acme.com"
5. $tpl with Embedded Directive Calls
A Liquid template can reference the same expression system inside its content using a BizFirst-aware Fluid filter or by structuring values into the template context before rendering:
-- Liquid template stored in DB (name: "WelcomeEmail") --
Dear {{ user.name }},
Welcome to {{ tenant.name }}. Your account was created on {{ exec.startedAt | date: "%B %d, %Y" }}.
Your default currency is {{ tenant.currency }}.
Regards, {{ platform.supportEmail }}
The $tpl directive builds the Liquid template context from EvaluationContext — tenant, user, exec, input, vars — before rendering.
6. Storing a Computed Value Back to Memory
A VariableAssignment node can use an expression as the value to store:
// VariableAssignment node config:
// VariableName: "discountedTotal"
// Value field: "{@ $js`return context.vars.subtotal * (1 - context.vars.discount)` }"
// The node executor evaluates the expression → gets a number
// Then stores it in memory as "discountedTotal"
// Downstream nodes can access it as {@ $var.discountedTotal }
7. API Call Result Used in Template
Chain: $api result → stored in $var → used in $tpl
8. Multi-Tenant Expression Override Pattern
Different tenants need different calculation logic. Use app-scoped canned expressions to override the base:
// Tenant-wide canned expression (AppId = NULL):
// Name="tax.calculate", Expression="{@ $js`return context.vars.amount * 0.20` }"
// App-specific override (AppId = 7 — Ireland region):
// Name="tax.calculate", Expression="{@ $js`return context.vars.amount * 0.23` }"
// Node field in workflow:
"@tax.calculate"
// Resolution:
// If AppId = 7 → uses 23% (Irish VAT)
// All other apps → uses 20% (UK VAT)
9. Parallel Fan-Out Aggregation
A ParallelFork node creates multiple execution lanes. Each lane processes items independently. A ParallelJoin node uses $items to aggregate results:
10. Debug / Audit Logging Template
A logging node that captures full execution state for audit: