Portal Community

When to Use

Function vs CodeExecute

FeatureFunctionCodeExecute
Execution timeoutConfigurable via Timeout setting (milliseconds)Hard-coded 5 seconds
SandboxModeConfigurable: true (restricted) or false (broader access)Always sandboxed (no control)
Result handlingIf result is a Dictionary/object, properties expand to individual OutputData keysresult always wrapped as OutputData["result"]
Best forMulti-field outputs, long-running scripts, configurable security contextsQuick single-value transformations within 5 seconds
Output access{{ $output.fnNode.fieldName }} when result is an object{{ $output.codeNode.result.fieldName }} always nested

Configuration

SettingRequiredDescription
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 valueOutputData structureExpression 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

PortWhen It Fires
successThe script completes within the configured timeout without throwing an unhandled exception. OutputData contains either the expanded object properties or the wrapped result.
errorThe 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

ExpressionValue
{{ $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

PolicyRationale
Set Timeout explicitly in productionThe 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 workflowsDisabling 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 expansionThe 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 portRuntime 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 synchronousThe 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.