$js — JavaScript Directive
Executes an inline JavaScript snippet using the Jint engine. The script receives the full evaluation context as context and must return a value via return.
| Property | Value |
|---|---|
| Directive name | js |
| Required isolation level | Sandboxed (minimum) |
| Project | Scripting.Js.Services |
| Engine | Jint (ECMAScript 5.1 + ES6 subset) |
| Syntax form | Template literal: $js`...script...` |
| Max execution time | Configurable, default 5 seconds |
Syntax
Single expression — inline return
{@ $js`return context.input.current.amount * 1.2` }
Multi-line — full script block
{@ $js`
const rate = parseFloat(context.ctx.env.VAT_RATE || '0.20');
const net = context.input.current.amount;
return (net * rate).toFixed(2);
` }
Context object structure
The
context object exposes all directives as sub-objects: context.ctx, context.input, context.output, context.exec, context.flow, context.vars (for $var). Values are already resolved — no additional expression syntax needed inside the script.
Isolation requirement
$js requires at least Sandboxed isolation. Nodes configured with Safe isolation cannot evaluate JavaScript expressions. Configure the node's isolation level in its settings.
Context Object Reference
| Path | Equivalent directive | Notes |
|---|---|---|
context.ctx.tenant.* | $ctx.tenant.* | Tenant metadata |
context.ctx.user.* | $ctx.user.* | Authenticated user |
context.ctx.env.* | $ctx.env.* | Environment variables |
context.ctx.now | $ctx.now | ISO 8601 UTC string |
context.input.current.* | $input.current.* | Current item fields |
context.input.items | $input.items | Full input array |
context.output.NodeKey.* | $output.NodeKey.* | Prior node output |
context.exec.* | $exec.* | Execution metadata |
context.flow.current.* | $flow.current.* | Workflow metadata |
context.vars.* | $var.* | Named variables |
Complex Scenarios
Scenario 1 — Graduated Tax Bracket Calculation
Multi-bracket UK income tax calculation that is too complex for simple math options:
{@ $js`
const income = parseFloat(context.input.current.annualSalary);
let tax = 0;
if (income > 150000) tax += (income - 150000) * 0.45;
if (income > 50270) tax += (Math.min(income, 150000) - 50270) * 0.40;
if (income > 12570) tax += (Math.min(income, 50270) - 12570) * 0.20;
return tax.toFixed(2);
` }
// Returns precise UK income tax for any salary
Scenario 2 — JSON Transformation and Field Remapping
Reshape a webhook payload to match an internal API contract:
{@ $js`
const wh = context.output.WebhookReceive.body;
return JSON.stringify({
externalId: wh.id,
customerRef: wh.customer_reference,
lineItems: (wh.lines || []).map(l => ({
sku: l.product_code,
qty: parseInt(l.quantity, 10),
unitCost: parseFloat(l.unit_price)
})),
total: wh.grand_total,
currency: context.ctx.tenant.currency
});
` }
Scenario 3 — String Manipulation and Validation
Validate and normalise an IBAN before sending to a banking API:
{@ $js`
const raw = (context.input.current.iban || '').replace(/\s/g, '').toUpperCase();
const iban = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$/.test(raw) ? raw : null;
if (!iban) throw new Error('Invalid IBAN: ' + raw);
return iban;
` }
// Throws → marks node failed → CatchBlock picks up the error
Scenario 4 — Date Arithmetic
Calculate invoice due date by adding payment terms days to the invoice date:
{@ $js`
const invoiceDate = new Date(context.input.current.invoiceDate);
const termsDays = parseInt(context.vars.paymentTermsDays || 30, 10);
invoiceDate.setDate(invoiceDate.getDate() + termsDays);
// Return ISO date string (YYYY-MM-DD)
return invoiceDate.toISOString().split('T')[0];
` }
Scenario 5 — Array Aggregation with Business Logic
Calculate weighted average unit cost from a batch of purchase lines:
{@ $js`
const lines = context.input.current.purchaseLines || [];
const totalQty = lines.reduce((s, l) => s + l.qty, 0);
const totalCost = lines.reduce((s, l) => s + (l.qty * l.unitCost), 0);
if (totalQty === 0) return '0.00';
return (totalCost / totalQty).toFixed(4);
` }
// "12.3456" — weighted average unit cost across all lines
Scenario 6 — Conditional Routing with Complex Logic
A SwitchNode uses JavaScript to determine the routing key based on multiple field conditions:
{@ $js`
const item = context.input.current;
const amount = parseFloat(item.amount || 0);
const region = (item.region || '').toUpperCase();
const isVip = (context.vars.vipList || []).includes(item.customerId);
if (isVip && amount > 10000) return 'vip_high_value';
if (isVip) return 'vip_standard';
if (region === 'US' && amount > 5000) return 'us_compliance_review';
if (amount > 50000) return 'finance_approval';
return 'auto_approve';
` }
Common Errors
| Error | Cause | Fix |
|---|---|---|
IsolationViolation: $js requires Sandboxed | Node isolation level is Safe | Set node isolation to Sandboxed or Trusted in node settings |
ScriptTimeout: exceeded 5000ms | Infinite loop or heavy computation in script | Optimise script; increase timeout via Expressions:JsTimeoutMs config |
ScriptError: Cannot read property 'x' of undefined | Accessing a context path that doesn't exist | Guard with || {} or null-checks before property access |
ScriptError: throw | Explicit throw in script | Intentional — CatchBlock will receive the error message; use for validation |
Returns [object Object] | Returning an object without stringifying | Use JSON.stringify(obj) or use @json option on the expression |