Custom Code Execution
Run JavaScript or Python in your workflows with sandboxed execution, workflow context access, and structured output
Run JavaScript (Node.js 24) or Python 3.13 code inside your workflows. Custom code nodes execute in isolated sandboxes with access to workflow variables and trigger data. Output is captured from stdout and passed to downstream nodes.
Supported runtimes
| Runtime | Identifier |
|---|---|
| JavaScript | Node.js 24 |
| Python | Python 3.13 |
Select the runtime in the workflow builder when configuring a custom code node. Only standard library capabilities are available -- the sandbox does not install third-party packages.
Workflow context
Your code receives a context.json file containing the current workflow state:
variables-- Current workflow variable values.trigger-- The data that started the workflow (webhook payload, schedule metadata, etc.).env-- Environment variables explicitly configured for the sandbox.
JavaScript:
const fs = require("fs");
const context = JSON.parse(fs.readFileSync("./context.json", "utf8"));
const name = context.variables.customerName;
const payload = context.trigger;
Python:
import json
with open("context.json") as f:
context = json.load(f)
name = context["variables"]["customerName"]
payload = context["trigger"]
Returning output
Print JSON to stdout. The output is parsed and made available to downstream nodes as structured data.
// Structured output (downstream nodes receive the parsed object)
console.log(JSON.stringify({ total: 42, status: "processed" }));
import json
print(json.dumps({"total": 42, "status": "processed"}))
Non-JSON stdout is returned as { rawOutput: string }. stderr is captured separately for debugging but not passed to downstream nodes.
Empty stdout produces null output.
Resource limits
| Limit | Value |
|---|---|
| Maximum code size | 50 KB |
| Default timeout | 120 seconds |
| Minimum timeout | 1 second |
| Maximum timeout | 300 seconds |
Timeout is configurable per node in the workflow builder. If the configured value falls outside the 1--300 second range, it is clamped to the nearest boundary.
Error codes
When execution fails, the node produces an error with one of these codes:
| Code | Exit code | Meaning |
|---|---|---|
OOM_KILLED | 137 | Process exceeded memory limits |
TIMEOUT | 143 | Execution exceeded the configured timeout |
EXECUTION_FAILED | Non-zero | Unhandled exception in user code |
SANDBOX_ERROR | -1 | Infrastructure failure (rare) |
The error message includes stderr output for debugging EXECUTION_FAILED cases.
Credit cost
Each custom code execution costs 5 Workflow Credits. This applies regardless of runtime, duration, or whether the code succeeds or fails. See billing for credit quotas per plan.
Security model
Code runs in an isolated Vercel Sandbox:
- No access to the host environment or other workflows.
- No network access to internal services.
- Credentials are only available via explicitly configured environment variables.
- A fresh sandbox is created per execution -- no state persists between runs.
- The sandbox is destroyed after execution completes, even on failure.
Example: enrich and route
JavaScript -- Read trigger data, compute a result, output structured JSON:
const fs = require("fs");
const context = JSON.parse(fs.readFileSync("./context.json", "utf8"));
const order = context.trigger;
const total = order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
const tier = total > 500 ? "priority" : "standard";
console.log(JSON.stringify({ total, tier, itemCount: order.items.length }));
Python -- Same logic:
import json
with open("context.json") as f:
context = json.load(f)
order = context["trigger"]
total = sum(item["price"] * item["qty"] for item in order["items"])
tier = "priority" if total > 500 else "standard"
print(json.dumps({"total": total, "tier": tier, "itemCount": len(order["items"])}))
A downstream condition node can branch on tier to route priority orders to a human approval step and standard orders to automatic fulfillment.