Your own JavaScript
The code_javascript node runs your JavaScript as a graph step. It is the last door: the thing there is no node for and no template can express.
First — do not write code
The default in Flow is a node, not code, and that is not ideology. A node is visible on the canvas, the validator sees it, its configuration lands in the run log, its failures are classified into codes, and the next person understands the graph without reading a program. Code has none of that: it is opaque to every tool the platform has, and it costs an extra credit per run.
| Task | What to use | Where to read |
|---|---|---|
| Assemble a string from several values | template | Templates |
| Parse JSON, pull out a field | json_parser | Templates |
| Branch on a condition | if / switch | Branching logic |
| Walk a list | while_loop / array nodes | Loops |
| Add, count, compare numbers | logic and string nodes | Nodes: overview |
| Call an external API | http_request | HTTP and the web |
| Tabular data, CSV, aggregates | df_* | Dataframes |
Links for the last column: Templates, Branching logic, Loops, Nodes: overview, HTTP and the web, Dataframes.
Code earns its place where an expression stops being an expression: a non-trivial format to parse, a recursive walk over a tree, an algorithm with state and loops, stitching a dozen fields together by rules that are easier to write than to draw. The tell that it is time: a template with more than two nested conditions in it.
What arrives and what to return
You write a function body, not a file. The platform wraps it like this:
async function call(input, ...extraPorts) {
// your code
}
inputis the value that arrived on the Run port. A string, a number, an object — whatever the data wire delivered.- Every extra input port you add on the inputs tab becomes a parameter of the same name. Names are sorted alphabetically, so the order is predictable.
- Whatever you return is the node’s output:
{{ nodes.<id>.output }}is exactly the returned value. Nothing needs wrapping. console.logand friends land in the node’s run log — that is the supported way to debug.get_var("name")/set_var("name", value)read and write variables declared by var nodes in the same graph. An unknown name is an error, not a silentundefined(State).
- Execute + Data
One more thing that reads as a bug: templates are not substituted inside the code body.
A {{ secret.KEY }} written in the code stays literal text. The code field is deliberately
not templated — otherwise JavaScript’s braces would need escaping, and a substituted secret
would end up in the run log. If the code needs a key, add an input port and feed the value
in from a separate node.
The main point: your code does not run inside the platform
Your JavaScript executes in a separate container that holds nothing but a runtime.
- The platform computes the whole security policy up front — allowed hosts, denied address ranges, time and memory ceilings, the code-size limit.
- The finished job is sent to the executor container. It decides nothing: the permission set your code runs under is handed to it, not chosen by it, and anything outside that set is rejected.
- Every run gets a fresh process. The result and the
console.*lines stream back.
What that container has and does not have:
| What | Present? |
|---|---|
| Platform code, its configuration | no |
| Workspace secrets, model keys | no |
| Access to the database, the queue, the vector store | no — it is not even on that network |
| Workspace files, chat attachments | no |
| Write access to the filesystem | no, the filesystem is read-only |
| Outbound network | yes, but under policy — see below |
| Memory and CPU ceilings | yes, the container is hard-capped |
Why bother, if the code is yours anyway. Because “your code” means code you wrote and the platform executes, on machines where other customers’ data lives. One bug in a JavaScript engine is enough for a run to escape its sandbox. Had it been running inside the platform’s worker process, that escape would mean access to the database, to other people’s secrets and to other people’s files. A separate container turns the same escape into landing in a disposable empty box with no route to the database and not a single secret inside. The isolation costs us deployment complexity and costs you an administrator who has to run one more service; that is exactly why code execution does not turn on without it.
Network
fetch from code works. By default any public address is reachable; the Allowed hosts
field narrows that to the hostnames you list. Private and loopback addresses are blocked
always, regardless of the setting — the same guard as on
http_request, and for the same reason: code that received an
address from a user’s message must not become a scanner of the internal network.
Limits
| What is capped | Value |
|---|---|
| Time per run | 5 seconds by default, ceiling 30 |
| Memory | 128 MB by default, ceiling 512 |
| Code size | 64 KiB |
| Concurrent runs per executor | capped; over capacity the node fails rather than queues |
Exceeding the time budget is its own distinct failure — “the code did not finish in time”, not a generic error — so the log tells you what to fix.
Errors and billing
The error output on this node is off by default (unlike http_request): your own code
failing is usually a defect to fix, not an expected outcome to handle. Turn it on with the
Error output toggle in the node’s main field list (Node errors).
The sandbox’s own message is shown to the run’s author: it is their code and their bug. It is never shown to the person in the chat.
What next
Nodes: overview
What you can do without opening the code editor.
Templates
Substitutions — the first tool, well before any code.
State
The variables code reads through get_var/set_var.
HTTP and the web
Calling out without a single line of code.
Credits
What a run's price is made of.
Self-hosting
Who enables code execution, and how.