Skip to content

Custom Cloud Code

This block runs a snippet of JavaScript as a step in your flow. You pass values in as named arguments, write your logic, and the value you return becomes the block's result.

How it works

Each time the flow reaches this block, your code runs once, on the server, as a single step. Data flows in through the Arguments list on the block's configuration panel. Each argument is a name plus a value - typically an expression that reads a previous step's result. Press Open Code Editor to write the code; inside the Code Editor, each argument name is a ready-to-use top-level variable. The values arrive already typed, so an array stays an array you can call .filter() on. Whatever your code returns becomes the block's result, which later blocks read in the Expression Editor under the block's result alias - by default, Custom Cloud Code Result.

The code runs in its own isolated environment, created fresh on every run - nothing you set persists from one run to the next.

When to use it

Reach for it when a few lines of code would be cleaner than wiring up several blocks to do the same thing. Reshaping an API response, computing a value, or combining two blocks' outputs is often a short snippet, where the no-code equivalent might be a chain of Transform Data blocks that clutters the flow. It is also how you do something genuinely custom that no built-in block offers. The code can even reach the web itself with fetch, but save that for the occasional call a snippet genuinely needs: a dedicated block like HTTP Request shows its call on the canvas, records it in run history, and can retry on failure, none of which a fetch buried in code gets you. You will find the block in the Actions group of the block palette.

Example

Suppose an upstream Fetch Orders block (an HTTP Request) fetched a list of orders, and the rest of the flow needs one question answered: how many orders are still open, and which ones - a count to branch on, and the ids to save for the follow-up. Say the fetched list looks like this:

[
  { "id": 1002, "status": "open" },
  { "id": 1003, "status": "shipped" },
  { "id": 1005, "status": "open" }
]

This block - named Count Open Orders here - answers that question. In its Arguments list, add one argument named orders, and for its value pick the Fetch Orders block's result in the Expression Editor. Then write the code in the Code Editor, opened with Open Code Editor:

// "orders" is the array returned by the HTTP Request block
const open = orders.filter(order => order.status === "open");
return {
  openCount: open.length,
  openIds: open.map(order => order.id),
};

The screenshot below shows the editor with orders bound beside this code:

The Code Editor: an argument named orders bound to the HTTP Request result, alongside the code that uses it.

Against the sample list above, the code returns this - the block's result:

{
  "openCount": 2,
  "openIds": [1002, 1005]
}

Downstream blocks read the result through its alias - left at its default, Custom Cloud Code Result. In this flow, an Any Open? block (a Condition) checks whether Custom Cloud Code Result → openCount is greater than 0, and its Yes branch leads to Save Open Order Ids (a Set Variables block), which stores Custom Cloud Code Result → openIds in an Open Order Ids variable - ready for whatever notification or reporting step the flow adds next. The count and ids the code computed drive the rest of the flow. On the canvas, the chain reads left to right:

The wired chain on the canvas: a Fetch Orders HTTP Request block into the selected Count Open Orders Custom Cloud Code block, into an Any Open? Condition whose Yes branch leads to a Save Open Order Ids block. Beside the canvas, the selected block's configuration panel shows its Name, the Open Code Editor button, the orders argument bound to the HTTP Request Result, and the result alias.

What the code can use

The runtime is the isolated JavaScript environment described above. Modern language features are all there, and a set of familiar globals comes with them:

  • fetch - real network access. It works as it does in a browser: pass headers as a plain object and the body as a string, and read the response with .json() or .text(). There are no Headers, FormData, or Blob constructors, so stick to plain objects and strings.
  • crypto - randomUUID(), getRandomValues(), and the WebCrypto subtle API.
  • Buffer - base64 and hex conversions, and binary data generally.
  • Timers - setTimeout and setInterval really wait. To pause your code, wrap one in a Promise and await it: await new Promise(r => setTimeout(r, 300)).
  • Text and URLs - TextEncoder and TextDecoder, atob and btoa, structuredClone, URL and URLSearchParams, and AbortController.

These globals are everything the runtime provides - anything they do not cover still comes into the flow through blocks. What is deliberately not here is Node.js itself; the exact list is under Limitations below.

As an AI Agent tool

Custom Cloud Code can also be given to an AI Agent as a tool, so the agent runs a snippet on its own when it decides it needs to - to do a calculation or reshape some data mid-task. You attach it from the agent's Manage Capabilities window, where it is listed under Utils. The tool then appears in the Tools row on the agent block, and selecting it there opens the tool's own configuration.

A tool turns the block's usual data flow around: instead of the flow handing every value in, the agent supplies what you leave open. Leave the code or an argument value empty and the agent provides it at runtime; a filled value is locked - the agent cannot see or override it.

In the tool's Code Editor, each argument gains two extra fields under Prepare for AI Agent:

  • Value Type - the data type the agent must supply, such as a string or a number.
  • Description - what the argument is for, written for the agent. Together with the type, this is how the agent knows what to pass, and when to reach for the tool.

As an ordinary block step, none of this appears: an argument is just a name and a value.

The Code Editor's Arguments panel for a Custom Cloud Code tool: an argument's name and value fields above a Prepare for AI Agent section with a Value Type dropdown and a Description field that tell the agent what to pass.

Configuration

Field Description
Arguments The values your code can use. Each argument has a name and a value, where the value is either static or an expression that references a previous block's result. Inside the code, each argument is available as a top-level variable of the same name.
Code The JavaScript to run. Press Open Code Editor to write it in the full-screen Code Editor, where the Arguments panel sits beside the editor. Return the value that becomes this block's result.

Common settings (available on most blocks):

Field Description
Name A label for this block on the canvas.
Reference Result Data As The alias used to reference this block's result in later blocks.
Assign to a Variable Optionally store the result in a Data Bucket variable too; you choose the bucket and the variable name.
Skip Block When on, the block is skipped during execution and the value in Simulated Result is used as its output.
Logging What to log to the Logging panel while the flow is LIVE, both on start and on completion.
Notes Freeform notes for documenting the block; they do not affect execution.

Behavior

  • async/await and Promises are supported; a returned Promise is awaited and unwrapped automatically before the result is captured.
    return Promise.resolve({ count: 3 });   // result is { count: 3 }
    
  • fetch makes real calls; await it and read the reply from the Response.
    const reply = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
    return await reply.json();
    
  • Throwing an Error sets the block's status to Error; the message keeps the error's name as a prefix.
    throw new Error("orders is empty");   // the block fails with "Error: orders is empty"
    
  • A thrown bare string also fails the block, with that string as the message - though a real Error object remains better style.
    throw "nope";   // the block fails with "nope"
    
  • Even null passes through as-is; nothing is converted to text on the way in.
    return Array.isArray(orders);   // true when orders came from an array-returning block
    
  • Any JSON-serializable return value works, scalars included.
    return 42;   // the block's result is 42
    

Limitations

  • You cannot load external libraries or packages - the globals listed under What the code can use are everything available. There is no require, no import, no process, and none of the Node modules (fs, http, zlib, and the rest).
  • The server runs in UTC with the en-US locale. Date and number formatting for other locales still works - Intl carries the full locale data.
  • The result is serialized as JSON. Map, Set, and RegExp become empty objects; NaN and Infinity become null; and undefined, functions, and Symbols are dropped.
  • Code runs in non-strict (sloppy) mode - assigning to an undeclared variable does not throw, so a typo in a variable name can fail silently.
  • Time is not the tight limit - runs that compute or wait for over a minute complete fine. Memory is: around 100MB of your own allocations is safe, but a couple of hundred megabytes crashes the execution environment, and the block fails with "Cloud Code execution was interrupted".
  • A value that cannot be JSON-serialized - a BigInt, or an object with a circular reference - fails the block with "Return value is not JSON-serializable", naming what could not be converted.
  • An un-awaited promise rejection crashes the execution environment and fails the block the same way. Await every promise, or attach a .catch().