When to Use
- Complex multi-step processing pipelines: An order enrichment function fetches pricing tiers, applies discounts, calculates tax, and assembles a complete order summary object — all in a single Function node with a 30-second timeout allowance.
- Multi-field result expansion: Unlike CodeExecute (which wraps result in a single
result field), Function allows the script to return an object whose properties become individual OutputData fields. Downstream nodes access {{ $output.fnNode.orderId }}, {{ $output.fnNode.total }}, {{ $output.fnNode.itemCount }} directly without nested access.
- Long-running data processing with custom timeouts: Processing a large dataset may legitimately take 15-30 seconds. CodeExecute's hard 5-second limit is too restrictive. Function's configurable
Timeout allows this safely.
- Pricing engine implementation: A multi-tier pricing calculation with volume discounts, currency conversion, and margin floor enforcement is best expressed in JavaScript. The Function node handles the complete calculation and returns a structured result object.
- Multi-field data validation returning structured errors: A form submission validator checks 10 fields and returns both a
valid flag and a detailed errors array. Result expansion makes both fields directly accessible downstream.
Function vs CodeExecute
| Feature | Function | CodeExecute |
| Execution timeout | Configurable via Timeout setting (milliseconds) | Hard-coded 5 seconds |
| SandboxMode | Configurable: true (restricted) or false (broader access) | Always sandboxed (no control) |
| Result handling | If result is a Dictionary/object, properties expand to individual OutputData keys | result always wrapped as OutputData["result"] |
| Best for | Multi-field outputs, long-running scripts, configurable security contexts | Quick single-value transformations within 5 seconds |
| Output access | {{ $output.fnNode.fieldName }} when result is an object | {{ $output.codeNode.result.fieldName }} always nested |
Configuration
| Setting | Required | Description |
Script |
Required |
The JavaScript code to execute. All workflow variables are available as globals. Must assign a value to the result variable. If result is assigned an object or Dictionary, its properties are expanded into individual OutputData keys. If result is any other type (string, number, array, boolean), it is wrapped as OutputData["result"]. |
Timeout |
Optional |
Integer, milliseconds. The maximum time the script is permitted to run. If the script exceeds this duration, it is terminated and routes to the error port with error_type: "Timeout". No default — if omitted, the platform default timeout applies (typically 30,000 ms / 30 seconds). Set explicitly for any production function with known performance characteristics. |
SandboxMode |
Optional |
Boolean, default true. When true, restricts the script to safe globals only (no network, file system, or process access). When false, broader globals may be available depending on the platform deployment configuration. NEVER set false in multi-tenant environments where workflows are authored by external parties. |
Result Expansion Behaviour
Key differentiator from CodeExecute: When the Function node's result variable is assigned an object (Dictionary), each key of that object becomes a top-level field in OutputData. Downstream nodes can reference these fields directly without nested access.
| Result value | OutputData structure | Expression to access |
result = { orderId: "ORD-001", total: 150.0 }; |
OutputData has orderId and total as top-level keys |
{{ $output.fnNode.orderId }} |
result = "hello"; (string) |
OutputData has single key result = "hello" |
{{ $output.fnNode.result }} |
result = [1, 2, 3]; (array) |
OutputData has single key result = [1, 2, 3] |
{{ $output.fnNode.result }} |
result = 42; (number) |
OutputData has single key result = 42 |
{{ $output.fnNode.result }} |
Output Ports
| Port | When It Fires |
success | The script completes within the configured timeout without throwing an unhandled exception. OutputData contains either the expanded object properties or the wrapped result. |
error | The script throws an unhandled exception, exceeds the timeout, contains a syntax error, or violates the sandbox policy. Error output includes error_message, error_type, and the line number. |
Sample Script — Order Summary with Result Expansion
// input is available as the workflow's current InputData
// Workflow variables (orderItems, customerId) injected as globals
var items = orderItems; // array of { productId, price, quantity }
var subtotal = items.reduce(function(sum, item) {
return sum + (item.price * item.quantity);
}, 0);
var tax = subtotal * 0.15;
var total = subtotal + tax;
// Assign an OBJECT to result — properties expand to individual output fields
result = {
orderId: input.orderId,
customerId: customerId,
itemCount: items.length,
subtotal: Math.round(subtotal * 100) / 100,
tax: Math.round(tax * 100) / 100,
total: Math.round(total * 100) / 100,
currency: input.currency || "USD"
};
Downstream expressions: {{ $output.orderSummaryFn.total }}, {{ $output.orderSummaryFn.itemCount }}, {{ $output.orderSummaryFn.orderId }} — all available as direct fields.
Sample Script — Multi-Field Validation
// formData injected as a workflow variable
var form = formData;
var errors = [];
if (!form.firstName || form.firstName.trim().length < 2) {
errors.push({ field: "firstName", message: "First name must be at least 2 characters." });
}
if (!form.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
errors.push({ field: "email", message: "A valid email address is required." });
}
if (!form.phone || !/^\+?[\d\s\-]{7,15}$/.test(form.phone)) {
errors.push({ field: "phone", message: "Phone number format is invalid." });
}
result = {
valid: errors.length === 0,
errorCount: errors.length,
errors: errors,
submittedBy: form.submittedBy
};
Sample Script — Pricing Engine with Tiered Discounts
// orderLines injected from workflow variable; customerTier injected as string
var lines = orderLines;
var tier = customerTier;
var subtotal = lines.reduce(function(s, l) { return s + l.unitPrice * l.qty; }, 0);
var discountRate = 0;
if (tier === "platinum") { discountRate = 0.20; }
else if (tier === "gold") { discountRate = 0.12; }
else if (tier === "silver") { discountRate = 0.05; }
var discount = Math.round(subtotal * discountRate * 100) / 100;
var afterDisc = subtotal - discount;
var vat = Math.round(afterDisc * 0.20 * 100) / 100;
var grandTotal= Math.round((afterDisc + vat) * 100) / 100;
result = {
subtotal: subtotal,
discountRate: discountRate,
discountAmt: discount,
vatAmt: vat,
grandTotal: grandTotal,
currency: "GBP"
};
Sample Configuration
{
"functionCode": "// Transform customer records for CRM sync\nconst records = items.map(item => ({\n externalId: item.json.id,\n fullName: `${item.json.firstName} ${item.json.lastName}`,\n email: item.json.email.toLowerCase(),\n tier: item.json.totalSpend > 10000 ? 'gold' : 'standard',\n syncedAt: new Date().toISOString()\n}));\nreturn records.map(r => ({ json: r }));",
"timeout": 10000
}
Sample Output
Success Port
{
"externalId": "cust_00451",
"fullName": "Alice Chen",
"email": "alice.chen@acmecorp.com",
"tier": "gold",
"syncedAt": "2025-03-15T10:30:00.000Z"
}
Expression Reference
| Expression | Value |
{{ $output.fnNode.total }} | Direct access to the total property when result was an object. |
{{ $output.fnNode.valid }} | Boolean valid field from a validation function result. |
{{ $output.fnNode.errors }} | The errors array from a validation function. |
{{ $output.fnNode.result }} | When result was a non-object type (string, number, array), access via the result key. |
Node Policies & GuardRails
| Policy | Rationale |
Set Timeout explicitly in production | The platform default timeout may change between platform updates. Always set Timeout to a value you have measured against expected data volume to prevent surprise timeout failures under load. |
Keep SandboxMode: true in all multi-tenant and externally-authored workflows | Disabling the sandbox in environments where workflow authors are not fully trusted allows scripts to read environment variables, make network calls, or access the file system — creating serious security risks. |
Return an object from result to benefit from field expansion | The key advantage of Function over CodeExecute is result expansion. If your script computes multiple values, always return them as a single object so downstream nodes can access each value directly without nested expressions. |
| Always handle the error port | Runtime exceptions and timeout errors both route to the error port. In production, connect it to a notification or dead-letter queue node. Include the error_message and error_type fields in your alerting payload for rapid diagnosis. |
| No async/await — all script logic must be synchronous | The Function node executes synchronously like CodeExecute. Async patterns do not work. All data that the script needs must be injected as workflow variables before execution — no in-script API calls are possible within the sandbox. |