Code
Switch a neuron's DO to Code and write JavaScript, TypeScript or Python that reads the neuron's inputs and returns its result.
With Code, a neuron's DO runs your own code instead of an AI model. The code reads the neuron's inputs, can call other services on the internet, and returns the neuron's result. Use it for exact work: maths, reshaping a table, calling an API or making a chart. Code runs in a private sandbox, and you pay for the time it runs.
Switch DO to Code
- Click DO on the neuron card.
- Under How DO works, click Code.
- Pick a language: JavaScript, TypeScript or Python.
The editor opens with starter code made from this neuron's inputs and its MAKE type. Your AI instructions are kept, so you can switch back at any time, as long as the neuron has no more than 3 inputs inputs: a Code neuron can have up to 5 inputs, and How DO works shows how many to remove before AI can be picked again. Changing the language replaces the code with that language's starter; if you had changed the code, you're asked first, and undo brings it back.
Work in the editor
- Click in the editor to type. A click below the last line puts the cursor at the end of the code.
- Copy code, the copy button above the editor, copies the whole code. After a test run, WHAT YOUR CODE RECEIVED, OUTPUT and LOGS each have a copy button that copies all of it, even when the box shows only the start.
- For more room, click Open expanded editor, beside Copy code. A large window opens with the editor and Inputs on the left, and Extra packages and Test run on the right. Everything works there as in the panel. Your code, the cursor and undo carry over when you close it with Close expanded editor, Esc, or a click outside it. If text is selected or suggestions are open, the first Esc clears them; press Esc again to close.
Write run(neuron)
Your code defines one function, run(neuron). Each run calls it once, and what it returns becomes the neuron's MAKE result.
async function run(neuron) {
const orders = neuron.input["orders"];
return orders.filter(row => row.Status === "Paid");
}
type Row = { Status: string; [column: string]: unknown };
async function run(neuron: { input: { orders: Row[] } }) {
return neuron.input["orders"].filter(row => row.Status === "Paid");
}
def run(neuron):
orders = neuron.input["orders"]
return orders[orders["Status"] == "Paid"]
What the neuron object holds
neuron.input["ID"]: each GET input, by its reference ID (see References). A table is a list of rows, each keyed by column name; in Python it is a pandas DataFrame. Text is a string, with any{{…}}references already filled in. A file (a PDF, an image or audio) has a name, path, type and size, and the file is ready to open at its path.neuron.memory["ID"]: the memories this neuron reads, as set under Memory in DO.neuron.secrets: your secrets.neuron.log(...): writes a line to the run log. Only you see it.neuron.make: what MAKE expects: its type, and its columns or conditions.neuron.rows.write(rows): writes a batch of rows to a Table MAKE. Use it for a table too large to return; see Write a large table in batches.neuron.inputInfo["ID"]: for each input, the total number of rows and whether the table was cut. A table longer than 10,000 rows is cut, so readinputInfobefore you add up or count a large table.
While you type neuron., the editor suggests what comes next, including this neuron's input IDs.
What to return
| MAKE type | Return |
|---|---|
| Table | a list of rows (in Python, a list of dicts or a DataFrame) |
| Text | a string |
| Decision | the name of one of MAKE's conditions, in any letter case |
| Image | PNG, JPEG, GIF or WebP bytes, or the path of an image file your code wrote |
| Audio | MP3 or WAV bytes, or the path of an audio file your code wrote |
| PDF bytes, or the path of a PDF file your code wrote |
If the code returns something else, the run fails and says what MAKE expected, for example "MAKE expects a table (a list of rows); your code returned text." Code works with any input, whatever MAKE type you pick.
Make a PDF
Only a Code neuron can make a PDF: pick PDF in MAKE, and your code builds the file. Neuron doesn't turn a result into a PDF for you. Return the PDF's bytes, or the path of a file your code wrote:
from fpdf import FPDF
def run(neuron):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=16)
pdf.cell(0, 10, "Weekly report")
return bytes(pdf.output())
async function run(neuron) {
const { PDFDocument } = await import("pdf-lib");
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage();
page.drawText("Weekly report", { x: 50, y: 700, size: 18 });
return await pdfDoc.save();
}
- A PDF can be up to 10 MB. Other outputs stay at 2 MB.
- A PDF with scripts, actions that run when it opens, attached files or a password is refused: the run fails with "MAKE expects a PDF without scripts or embedded files" and is still charged.
- The next neuron can read the PDF as a PDF input. An email in SEND attaches the file as it is, and an API destination in SEND sends it too when Attach is on (same for an image or audio file).
- To make a Word, Excel or PowerPoint file, pick Doc, Table or Slides instead; Neuron can attach those in SEND as a docx, xlsx or pptx.
Write a large table in batches
A table you return from run() is limited to 2 MB. For a bigger table, don't return it: call neuron.rows.write(rows) once for each batch of rows, and return nothing.
async function run(neuron) {
for (let start = 0; start < 800; start += 50) {
const batch = await scoreCandidates(start, 50); // your own function
neuron.rows.write(batch);
}
}
def run(neuron):
for start in range(0, 800, 50):
neuron.rows.write(score_candidates(start, 50)) # a list of dicts or a DataFrame
- MAKE must be a Table, and the code must not also return rows.
- A batch can hold up to 2 MB, so write smaller batches for wide rows. A run can write up to 100,000 rows.
- The first batch sets the table's columns. Later batches can leave a column out but can't add one.
- The rows are added to the table after the code ends, and the neuron's result shows only the first rows. The next neuron gets that short preview as its input, so read the full table from the table itself.
- If a batch fails, a table made by this run is removed. In a table that already existed, the batches written before the failure stay: add a
run_idcolumn so a rerun can find them. If the Document row in SEND is set to Replace all rows (see Replace the rows on every run), the table keeps its old rows after a failed run, and a run that succeeds swaps them for the batches. - Test run checks every batch and writes nothing.
Inputs
Under Inputs, below the editor, DO lists what your code gets: each GET input with its kind and a sample (a table shows its columns), the memories this neuron reads, and your secrets (names only). Click one to insert it at the cursor; click again to add it again after the first. If you haven't clicked in the editor yet, it goes at the end of the code, on a new line.
If you change an input's ID (Change ID), its neuron.input["…"] uses in this neuron's code are updated too; the dialog says how many.
Code gets the table's rows, up to 10,000 rows. If an input has Run once per row on, a Code neuron can't run, be tested or be published: DO and the neuron's to fix say "Turn off Run once per row, or loop over the rows in your code." Turn it off and loop over the rows in your code.
Test your code
Click Test run in the Test run section of DO, below the editor. It runs this neuron once with its current inputs: nothing is sent, no table is written and no neuron after it starts. While it waits for a free slot it says "Waiting for a code slot", then "Running code…" with the time; click Stop to end it. While it runs, the latest log lines show as they arrive.
When it finishes, the panel shows:
- WHAT YOUR CODE RECEIVED: the
neuronobject it got (names, kinds and a short sample; never a secret's value). It shows only on the page that started the run, while that page stays open, and isn't kept. - OUTPUT: what it returned, checked against MAKE's type.
- LOGS: what it wrote with
neuron.log(...)orprint. - How long it ran and the credits it cost. A test run costs the same as any other run.
If the code fails, the panel shows the error and the line it happened on. If the code received less than the full input, for example a table over the row limit, the panel says so.
Secrets
Keep API keys out of your code: add them on your Secrets page (account menu → Secrets; see Secrets), then read them as neuron.secrets:
const key = neuron.secrets.FX_API_KEY;
key = neuron.secrets["FX_API_KEY"]
The Inputs list shows your secrets' names. A secret's value is hidden from logs, errors and text outputs: where it appears, it shows as ••••. This hides the value as you typed it, and its common escaped forms, such as inside quotes in JSON or in a web address. It does not hide a value your code has changed into another form, such as base64, a part of the value, or a value your code splits across lines in an unusual way. Don't log those. A file your code makes (a PDF, an image or audio) isn't scanned, so a secret written into one is not hidden: keep secrets out of files. If your code reads a secret that isn't on your Secrets page, DO says so; runs fail until you add it.
Packages
Common packages are already installed. For Python: pandas, numpy, matplotlib, plotly, seaborn, scipy, scikit-learn, requests, aiohttp, httpx, beautifulsoup4, openpyxl, python-docx, pydantic, python-dateutil, pyyaml, tabulate, fpdf2, reportlab and pypdf. For JavaScript and TypeScript: lodash, date-fns, zod, papaparse, cheerio, csv-stringify and pdf-lib.
The PDF libraries (fpdf2, reportlab, pypdf and pdf-lib) are available when the v2 Code environment is selected for the deployment; if an import fails, add the one you use under Extra packages.
Under Extra packages, click See what's already installed for the list for each language.
To use another one, add its name under Extra packages, for example tqdm for Python or dayjs for JavaScript. A version is allowed, for example tqdm==4.66.5 or [email protected]. Extra packages are installed each time the code runs, which adds to its run time. Changing the language clears them.
DETECTED IMPORTS, under Extra packages, lists the packages your code imports, a moment after you type. One that is already installed, or already in Extra packages, shows with a check mark. One that isn't shows Add: click it to add it to Extra packages, or click Add all missing. Nothing is added until you click. When Extra packages is full, Add is greyed out and says so. Your own files and the language's built-in modules aren't listed.
On the card
A Code neuron's card shows CODE in DO and the start of the code. With no code yet, it says "No code yet: write it in DO." and the neuron shows "DO has no code" under to fix.
While the code runs, the card's bottom bar says "Waiting for a code slot" when every slot is busy, then "Running code…" with the time. Click Stop there to end the run.
Runs and logs
Code runs like any neuron: from Play, a schedule, a webhook, a neuron before it, or an agent. A run can take up to 30 minutes; you can close the tab meanwhile.
A neuron runs one piece of code at a time. If you start it again while its code is still going, you see "This neuron's code is still running." and nothing new starts.
Stop ends a Code run early, and the workflow's Stop run stops any Code run that is going too. The Code neuron ends in error with "Stopped.", no On error neuron starts, and you're charged for the time the code used before it stopped.
If a Code neuron fails in a scheduled, webhook or app event run, the run shows under Needs you on Home with Fix (see Needs you).
The run log in Runs shows each Code run's time and credits. Click Logs to see what the code wrote. Only you see logs; Users of an agent never see your code, logs or secrets, and a failed run shows them "Code step failed".
Queues
A Code neuron can't use Play all queued or Play through the queue: they are greyed out, and the tooltip says "Play through the queue isn't available for Code neurons. Loop over the items in your code." To run a list of values, loop over the items in your code. A single Play uses the neuron's current input and doesn't use up a queued value. An Agent that has a Code neuron can't run a list of values either; loop over the values in your code instead.
Internet and pricing
Your code can reach the internet, for example to call an API. Each run is billed for the time the code runs, including installing extra packages; a test run costs the same. Test run and the run log show what each run cost. A run that fails because of a problem on our side isn't charged. A run that is interrupted on our side is charged for the time it ran, up to the end of the time reserved for it. A run that never started its code isn't charged.
Examples
Convert currencies with an API call
A table input invoices with an Amount (USD) column, and MAKE set to Table.
Cells can be text, numbers or empty, so convert a cell to a number before adding or multiplying. Empty or non-numeric cells are treated as 0 here.
async function run(neuron) {
const res = await fetch("https://open.er-api.com/v6/latest/USD");
const { rates } = await res.json();
return neuron.input["invoices"].map(row => ({
...row,
"Amount (INR)": Math.round((Number(row["Amount (USD)"]) || 0) * rates.INR * 100) / 100,
}));
}
type Invoice = { "Amount (USD)": number | string | null; [column: string]: unknown };
async function run(neuron: { input: { invoices: Invoice[] } }) {
const res = await fetch("https://open.er-api.com/v6/latest/USD");
const { rates } = (await res.json()) as { rates: Record<string, number> };
return neuron.input["invoices"].map(row => ({
...row,
"Amount (INR)": Math.round((Number(row["Amount (USD)"]) || 0) * rates.INR * 100) / 100,
}));
}
import pandas as pd
import requests
def run(neuron):
invoices = neuron.input["invoices"]
rates = requests.get("https://open.er-api.com/v6/latest/USD", timeout=30).json()["rates"]
usd = pd.to_numeric(invoices["Amount (USD)"], errors="coerce").fillna(0)
invoices["Amount (INR)"] = (usd * rates["INR"]).round(2)
return invoices
Convert a large table in batches
A table input invoices with an Amount (USD) column that has too many rows to return in one go, and MAKE set to Table.
Write each batch with neuron.rows.write and return nothing. The run_id column marks the rows of this run, so a rerun can find and remove rows an earlier, failed run left behind.
async function run(neuron) {
const res = await fetch("https://open.er-api.com/v6/latest/USD");
const { rates } = await res.json();
const runId = new Date().toISOString();
const invoices = neuron.input["invoices"];
for (let start = 0; start < invoices.length; start += 500) {
neuron.rows.write(
invoices.slice(start, start + 500).map(row => ({
run_id: runId,
...row,
"Amount (INR)": Math.round((Number(row["Amount (USD)"]) || 0) * rates.INR * 100) / 100,
})),
);
}
}
import datetime
import pandas as pd
import requests
def run(neuron):
invoices = neuron.input["invoices"]
rates = requests.get("https://open.er-api.com/v6/latest/USD", timeout=30).json()["rates"]
run_id = datetime.datetime.now(datetime.timezone.utc).isoformat()
for start in range(0, len(invoices), 500):
batch = invoices.iloc[start:start + 500].copy()
usd = pd.to_numeric(batch["Amount (USD)"], errors="coerce").fillna(0)
batch["Amount (INR)"] = (usd * rates["INR"]).round(2)
batch.insert(0, "run_id", run_id)
neuron.rows.write(batch)
Total sales by region
A table input sales with Region and Amount, and MAKE set to Table.
import pandas as pd
def run(neuron):
sales = neuron.input["sales"]
sales = sales.assign(Amount=pd.to_numeric(sales["Amount"], errors="coerce").fillna(0))
totals = sales.groupby("Region", as_index=False)["Amount"].sum()
return totals.sort_values("Amount", ascending=False)
async function run(neuron) {
const totals = {};
for (const row of neuron.input["sales"])
totals[row.Region] = (totals[row.Region] || 0) + (Number(row.Amount) || 0);
return Object.entries(totals)
.map(([Region, Amount]) => ({ Region, Amount }))
.sort((a, b) => b.Amount - a.Amount);
}
Choose a decision branch
A text input amount, and MAKE set to Decision with the conditions Needs approval and Approve.
async function run(neuron) {
const amount = Number(neuron.input["amount"]);
return amount > 1000 ? "Needs approval" : "Approve";
}
def run(neuron):
amount = float(neuron.input["amount"])
return "Needs approval" if amount > 1000 else "Approve"
Make a chart
A table input sales with Month and Amount, and MAKE set to Image.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd
def run(neuron):
sales = neuron.input["sales"]
amounts = pd.to_numeric(sales["Amount"], errors="coerce").fillna(0)
by_month = amounts.groupby(sales["Month"]).sum()
fig, ax = plt.subplots(figsize=(8, 4))
by_month.plot(kind="bar", ax=ax, color="#7B337D")
ax.set_ylabel("Amount")
fig.tight_layout()
fig.savefig("chart.png", dpi=150)
return "chart.png"
Fill a text template
A table input order with Name, Order and Date, and MAKE set to Text.
async function run(neuron) {
const o = neuron.input["order"][0];
return `Hi ${o.Name}, your order ${o.Order} ships on ${o.Date}.`;
}
async function run(neuron: {
input: { order: Array<{ Name: string; Order: string; Date: string }> };
}) {
const o = neuron.input["order"][0];
return `Hi ${o.Name}, your order ${o.Order} ships on ${o.Date}.`;
}
def run(neuron):
o = neuron.input["order"].iloc[0]
return f"Hi {o['Name']}, your order {o['Order']} ships on {o['Date']}."
Remove duplicate rows
A table input leads with an Email column, and MAKE set to Table. Keeps the first row for each email, ignoring case and spaces. Rows with no email are dropped.
async function run(neuron) {
const seen = new Set();
return neuron.input["leads"].filter(row => {
const key = String(row.Email || "")
.trim()
.toLowerCase();
if (!key || seen.has(key)) return false;
seen.add(key);
return true;
});
}
def run(neuron):
leads = neuron.input["leads"]
key = leads["Email"].fillna("").astype(str).str.strip().str.lower()
return leads[~key.duplicated() & (key != "")]
Get help writing code
Ask Cortex in the canvas: describe what the neuron should do and it suggests code from this page's examples. Check the code with Test run before you rely on it.
Limits
- Inputs: up to 5 inputs per neuron.
- Code length: up to 100,000 characters.
- Run time: up to 30 minutes per run.
- Extra packages: up to 10 packages per neuron.
- Input rows: up to 10,000 rows.
- Logs: up to 50,000 characters.
- Output: up to 2 MB for a returned table or other result.
- PDF output: up to 10 MB.
- Table written in batches: up to 100,000 rows, and 2 MB in each batch.