Code Node
Run a short piece of JavaScript inline in a workflow. Perfect for formatting values, combining variables, calling a public API, or computing a quick calculation without setting up a full integration action.
Code Node vs Custom Integration Action
Staffify gives you two ways to run custom logic during a call. Pick the one that matches your use case.
| Code Node | Custom Integration Action | |
|---|---|---|
| Runs | JavaScript in Staffify sandbox | HTTP request to your server |
| Requires | Nothing — runs directly | Your own API endpoint |
| Best for | Formatting, calculations, quick public API lookups | Business writes, authenticated integrations, CRM sync |
| Max code size | 5,000 characters | N/A (runs on your server) |
| Secrets | Not safe — see security section | Managed by your backend |
Rule of thumb: reach for a Code Node when the work is lightweight — string formatting, arithmetic, or a read-only call to a public endpoint. For everything that touches your production systems, use a Custom Integration Action.
Adding a Code Node to your workflow
- In the workflow builder, drag Code from the left palette onto the canvas.
- Click the node to open its configuration panel on the right.
- Write JavaScript in the editor. You have access to the caller's workflow variables via
dv, call metadata viametadata, andfetch()for HTTP requests. - Return a JSON object with the values you want to keep.
- In Store fields as variables, list the fields from your returned object that should become workflow variables usable in later nodes.
- (Optional) Click Run Code in the test panel to try it with mock variable values before saving.
The JavaScript environment
Your code runs in an isolated V8 heap with a small set of globals. Standard built-ins like Math, JSON, Date,Array, and String all work. Top-level await is supported.
dv — workflow variables
Every workflow variable collected earlier in the flow is available under dv. Values are always strings, so use parseFloat or parseInt when you need a number.
const name = dv.caller_name; // "Jan de Vries"
const orderId = dv.order_id; // "78542"
const total = parseFloat(dv.amount); // convert to numbermetadata — call metadata
Any metadata attached to the session when the call started is available under metadata. Typical values include customer IDs or priority levels passed in from the API that initiated the call.
fetch(url) — HTTP requests
Make HTTP requests to public APIs. Works like the standard Fetch API but is limited to protect the sandbox from abuse (see limits below).
// GET request
const response = await fetch("https://api.example.com/data");
const data = await response.json();
// POST request
const response = await fetch("https://api.example.com/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: dv.caller_name })
});console.log()
Log values for debugging. Logs appear in the test panel and are stored in the execution log for every real call.
Limits
| Resource | Limit | What happens when exceeded |
|---|---|---|
| Code size | 5,000 characters | Node fails to save |
| Result size | 15,000 characters (serialized JSON) | Result is discarded, node marked failed |
| Wall-clock timeout | 5–60 seconds (default 30) | Execution killed, node marked failed |
| Memory | 32 MB per invocation | Execution killed with an OOM error |
| fetch() calls | 5 per invocation | Sixth call throws |
| fetch() body | 1 MB response | Response truncated, node fails |
| fetch() timeout | 10 seconds per call | Request aborted |
| Rate limit | 100 requests per hour per host per tenant | Further calls throw |
Storing values as variables
The Store fields as variables table lets you extract values from your returned JSON and save them as workflow variables. The field column accepts dot notation and array indexing.
| Field path | Example | Extracts |
|---|---|---|
| Top-level | status | result.status |
| Nested | data.order.id | result.data.order.id |
| Array element | items[0].name | First item's name |
| Nested array | orders[2].items[0] | First item of the third order |
If a path doesn't resolve, the variable is silently skipped — the workflow does not fail on missing optional fields.
Node settings
- Speak during execution: choose whether the agent stays silent (Off), lets the LLM improvise a short filler like “let me look that up for you” (Prompt), or speaks an exact sentence you provide (Static text). Useful when your code makes an API call that takes a few seconds.
- Play typing sound: plays a subtle typing loop while the code runs, signaling activity to the caller.
- Wait for result: when enabled the workflow waits for the code to finish before continuing. Turn off for fire-and-forget notifications where you don't need the result.
- Execution timeout: slider between 5 and 60 seconds. Default 30. Long-running code is killed and the execution logged as timeout.
Security and what NOT to do
Do not use Code Nodes for secrets. Both dv and metadata are stored in plaintext in every call log and API response. Anything you put there is visible to anyone with access to your call history.
- No secrets in variables. API keys, database credentials, session tokens — none of these belong in workflow variables. Use a Custom Integration Action instead, where the auth stays server-side.
- No production writes. The Code Node is designed for lightweight logic. Writing to your CRM, taking payments, or updating customer records should go through an Integration Action with proper audit logging and idempotency.
- Fetch is limited by design. Requests to private IP ranges (10.x, 192.168.x, 127.0.0.1) and known internal hostnames are blocked. AWS/GCP metadata endpoints (169.254.169.254) are blocked. Redirects are re-checked at every hop.
- No filesystem, no npm packages. The sandbox exposes only the standard JavaScript built-ins plus the globals listed above.
requireandimportare not available.
Examples
Format a customer's full name
// Combine first + last + tussenvoegsel into a single string
const parts = [dv.first_name, dv.tussenvoegsel, dv.last_name]
.filter(Boolean)
.join(" ");
return { full_name: parts };Calculate a total with VAT
const nights = parseInt(dv.nights, 10);
const rate = parseFloat(dv.rate);
const subtotal = nights * rate;
const vat = subtotal * 0.21;
return {
subtotal_eur: subtotal.toFixed(2),
vat_eur: vat.toFixed(2),
total_eur: (subtotal + vat).toFixed(2)
};Look up an address from postcode + house number
const response = await fetch(
`https://postcode.tech/api/v1/postcode?postcode=${encodeURIComponent(dv.postcode)}&number=${encodeURIComponent(dv.house_number)}`
);
const data = await response.json();
return {
street: data.street,
city: data.city,
province: data.province
};Decide routing based on customer tier
const tier = (dv.customer_tier || "standard").toLowerCase();
if (tier === "premium") {
return { route: "priority_queue", greeting: "premium" };
} else if (tier === "vip") {
return { route: "dedicated_agent", greeting: "vip" };
}
return { route: "general_queue", greeting: "standard" };Handling failures
When a Code Node fails (timeout, thrown error, out of memory, blocked fetch, etc.), the workflow does not halt. Instead a special variable is set that downstream nodes can branch on:
code_error — set to a short description of what went wrongYou can wire a Condition node right after the Code Node with a check on {{code_error}} is not empty to send the call down an error-handling path (e.g. a graceful apology + transfer).
Wrap risky code in a try / catch when you want to handle errors yourself without triggering the workflow-level fallback:
try {
const r = await fetch("https://api.example.com/thing/" + dv.id);
const data = await r.json();
return { status: "ok", value: data.value };
} catch (err) {
return { status: "failed", reason: String(err) };
}Execution history
Every Code Node invocation — real calls and Run Code tests — is logged to your account. Each row records the outcome status, duration, error message (if any), number of fetch() calls made, and the size of the returned result. This is intended for debugging and abuse monitoring.
The actual JavaScript source is not stored in the log — it lives on the workflow definition — so we don't duplicate it.