Portal Community

1. Dynamic HTTP Body Built from Multiple Directives

An HTTP Request node's body field uses multiple expressions assembled into a JSON payload.

Node field: Request Body (template string)
{
  "tenantId": "{@ $ctx.tenant.id }",
  "userId": "{@ $ctx.user.id }",
  "amount": {@ $var.invoiceTotal },
  "currency": "{@ @company.defaultCurrency }",
  "referenceId": "{@ $exec.executionId }"
}
{"tenantId":"5","userId":"42","amount":1250.00,"currency":"EUR","referenceId":"e8f2..."}

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:

Discount tier calculation
{@ $js` const total = context.vars.invoiceTotal; if (total > 10000) return 'platinum'; if (total > 5000) return 'gold'; if (total > 1000) return 'silver'; return 'standard'; ` }
"gold" (for invoiceTotal = 7500)

3. Cross-Node Data Aggregation

Downstream nodes reference output from multiple upstream nodes:

Merge data from two upstream nodes into an email subject
Invoice {@ $output.ParseInvoice.invoiceNumber } for {@ $output.FetchCustomer.company }{@ $output.ParseInvoice.total @json } due
"Invoice INV-2024-001 for Acme Corp — 4500 due"

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 }}
Node field
{@ $tpl.WelcomeEmail }
Fully rendered welcome email with all values substituted

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

Node 1 — HttpRequest: fetch exchange rate (stored to var.usdRate)
{@ $api.ExchangeService/rates/USD.rate @cache-thread }
Node 2 — VariableAssignment: store rate
Variable: usdRate   Value: [above expression]
Node 3 — Email: render with rate
Amount in USD: {@ $js`return (context.vars.totalEur * context.vars.usdRate).toFixed(2)` }

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:

ParallelJoin node — compute total from all parallel branches
{@ $items.sum.processedAmount }
Sum of processedAmount across all completed parallel lanes

10. Debug / Audit Logging Template

A logging node that captures full execution state for audit:

Audit log message field (template string)
[{@ $ctx.now }] Execution {@ $exec.executionId } on workflow "{@ $flow.current.name }" v{@ $flow.current.version } by user {@ $ctx.user.email } (tenant: {@ $ctx.tenant.name })
[2024-03-15T10:22:01Z] Execution e8f2... on workflow "Invoice Processing" v3 by alice@acme.com (tenant: Acme Corp)