Triggo Documentation
Code Node

Code Node Overview

Run JavaScript or TypeScript inside your workflow when no built-in node fits.

Code Node Overview

The Code node runs a short piece of JavaScript or TypeScript as a step in your workflow. It is the escape hatch for the moments when the template language in field mapping can't express what you need and no connector action fits either — a place to shape data, compute a derived value, or pick an element out of an array before handing the result to the next node.

It is deliberately narrow. The Code node does not make HTTP calls, does not touch the filesystem, and does not keep state between runs. Think of it as a pure function from inputs to an output value.

When to reach for it

Use a Code node when:

  • You need logic the template language can't express — conditionals, string manipulation beyond simple path access, arithmetic on multiple fields, picking an array element.
  • You want a utility from the built-in helpers: date math via dayjs, a UUID, pick/omit/groupBy/keyBy/uniqBy on an array, base64 encoding, or a hash (sha256 by default).
  • You need bespoke data shaping — flattening, renaming, combining fields from several upstream nodes into one object — that no connector action already does.

When not to use it

  • To call an external HTTP API — the Code node cannot make network calls. Use the HTTP Request connector, or a proper connector action if one exists.
  • For simple field mapping — if you just want to pass trigger.email into the next step, do that inline in the inspector. Don't wrap it in code.
  • For anything that needs state outside the run — no database, no cache, no files. Every execution starts from zero. If you need persistence, use a connector.

The sandbox model

Every Code node execution spawns its own V8 isolate via isolated-vm. This is not vm2, not Node's vm module, and not a worker thread — it's a fully separate V8 heap with no shared memory.

Defaults come from DEFAULT_SANDBOX_CONFIG in:

  • Memory limit: 64 MB per execution.
  • Wall-clock timeout: 30 seconds. This is the same 30 s step timeout the rest of the executor uses — exceeding it fails the step.
  • Fresh isolate per call: the sandbox is created in createCodeSandbox() and disposed immediately after the node returns. No state survives between invocations. A variable you "set" in one run is gone on the next.

What's blocked

The bootstrap script deletes the following globals before your code runs:

  • fetch — no HTTP.
  • setTimeout, setInterval, setImmediate, clearTimeout, clearInterval, clearImmediate — no timers. You cannot delay or poll.
  • process, require, import, filesystem, network — none of these exist in the isolate to begin with. There is no module loader. You cannot require("lodash") or dynamic-import() anything.

If you write await fetch(...) or setTimeout(...), the step fails with a ReferenceError. There is no workaround inside the Code node; move network calls to an HTTP Request action upstream and pass the result in via field mapping.

What's available

Inside the isolate you get:

  • console.log / .warn / .error / .info / .debug — all captured. The first 1 000 log lines per execution are recorded on the run and logged once at step end under consoleLogs. Beyond 1 000 lines, further logs are dropped silently.
  • __utils — a helper belt, available two ways (pick whichever reads better):
  • As a global: __utils.uuid(), __utils.dayjs(...), __utils.hash("foo").
  • As the second argument to your function: function run(inputs, utils) { utils.uuid(); }. The handler always invokes with [inputs, null] and the sandbox replaces the second slot with __utils right before the call.
  • Standard JS — everything V8 ships with: JSON, Math, Date, Object, Array, Map, Set, Promise, Buffer is not provided.

See reference.mdx for the full utility list and function signatures.

A minimal example

Every Code node defines one named function. The executor finds it by looking for (in order) export default function <name>, function run, or the first named function in the source. run is the convention.

Given a webhook trigger that fires with { name, email }, build a normalized display name and hand it on:

function run(inputs: { name: string; email: string }, utils) {
  const name = (inputs.name ?? "").trim();
  const email = inputs.email.toLowerCase();
  const display = name.length > 0
    ? `${name} <${email}>`
    : email;

  return {
    display,
    emailNormalized: email,
    contactId: utils.hash(email), // stable id from email
  };
}

Wire it up in the inspector by mapping name to {{trigger.name}} and email to {{trigger.email}}. The return value — the whole object — becomes the Code node's output. Downstream nodes address fields on it as {{code_1.display}}, {{code_1.contactId}}, and so on.

The function can be async or return a Promise; the sandbox awaits it. TypeScript type annotations are stripped at handler entry via Node 24's native stripTypeScriptTypes, so : string and : Record<string, any> are fine to write — but don't expect TS-level type checking, only syntactic stripping.

  • Reference — full API: entry function detection, I/O contract, utility signatures, limits.
  • Migrating from n8n — mapping n8n's Code/Function node concepts onto Triggo's.
  • Field mapping — the {{...}} template language for getting data into the Code node.

On this page