Portal Community
PropertyValue
Directive namejs
Required isolation levelSandboxed (minimum)
ProjectScripting.Js.Services
EngineJint (ECMAScript 5.1 + ES6 subset)
Syntax formTemplate literal: $js`...script...`
Max execution timeConfigurable, 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

PathEquivalent directiveNotes
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.nowISO 8601 UTC string
context.input.current.*$input.current.*Current item fields
context.input.items$input.itemsFull 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

ErrorCauseFix
IsolationViolation: $js requires SandboxedNode isolation level is SafeSet node isolation to Sandboxed or Trusted in node settings
ScriptTimeout: exceeded 5000msInfinite loop or heavy computation in scriptOptimise script; increase timeout via Expressions:JsTimeoutMs config
ScriptError: Cannot read property 'x' of undefinedAccessing a context path that doesn't existGuard with || {} or null-checks before property access
ScriptError: throwExplicit throw in scriptIntentional — CatchBlock will receive the error message; use for validation
Returns [object Object]Returning an object without stringifyingUse JSON.stringify(obj) or use @json option on the expression