Code editor
The Code editor step runs Python inside your workflow and passes on whatever your code prints. Use it for the calculations, reshaping and business rules that the visual steps do not cover.
Look for it as βCode editorβ in the dock. That is the label it carries there, under Tools β Data β Dev tools.
Keyboard shortcut: t+c
The step runs Python 3.13, with the standard library plus numpy and pandas available. The Language field reflects that and is fixed.
Setting it up
Add the step
Press t+c, or open Tools in the dock and select Code editor under Dev tools.
Write your Python
Reference earlier steps with {{ }} placeholders, on their own and without quotes. The section below covers the syntax.
Print what the next step needs
The step passes on what you print, not what you return.
Run the workflow to see the result
Open the stepβs Executions tab to read the output.
Reading data from earlier steps
Reference an earlier step by its number, the same as in any other field, and write the reference on its own and unquoted:
customer_name = {{ 2.customer_name }}
order_total = {{ 2.total }}
products = {{ 3.$full_result }}Each reference arrives as a real Python value, not as text that gets pasted into your program. A JSON object becomes a dict, a list becomes a list, and true / false / null become True / False / None. Nothing needs quoting or escaping, and a customer called O'Brien cannot break your code.
{{ 3.$full_result }} gives you a whole stepβs output as one value, which is the usual way to get a list of records to loop over.
βReference is inside a longer piece of textβ
If the step fails with that message, this is the cause and the fix.
A reference has to be a value on its own. Written inside a longer piece of text it has no correct meaning, so the step refuses to run rather than guessing:
print("Hello {{ 2.name }}")Assign it first and build the text afterwards:
name = {{ 2.name }}
print(f"Hello {name}")The same applies inside an f-string or any other quoted run, and to every kind of reference, {{ $var.KEY }} and {{ $secret.KEY }} included. Assign first, then use the variable.
Full placeholder syntax, including {{ $now }} and {{ $var.KEY }}, is documented in Variable Reference Syntax.
Available libraries
The step runs Python 3.13 with the full standard library, plus two data libraries. Import them as normal: there is nothing to install and nothing to declare.
The two data libraries
| Library | Version | For |
|---|---|---|
| numpy | 2.5.1 | Numeric arrays and mathematics |
| pandas | 2.3.3 | Tables, grouping, joins and summarisation |
Both versions are pinned rather than tracking the latest release, so the same program keeps producing the same result.
The standard library, by what you came to do
Everything in Python 3.13βs standard library imports, which is far more than most workflows need. These are the modules worth knowing about:
| You want to | Import |
|---|---|
| Read or write JSON | json |
| Do date and time arithmetic | datetime β and zoneinfo for time zones |
| Match or replace text patterns | re |
| Read or write CSV | csv (with io.StringIO for text you already have) |
| Hash or sign something | hashlib, hmac |
| Encode for an API | base64, urllib.parse for query strings and escaping |
| Count, group or deduplicate | collections β Counter, defaultdict |
| Averages, medians, standard deviation | statistics |
| Money and exact decimals | decimal β avoids the rounding errors of ordinary floats |
| Generate an ID | uuid, secrets |
| Combine or chunk lists | itertools, functools |
| Compress or unpack an archive | gzip, zipfile, tarfile |
import json
import re
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from collections import Counter
import pandas as pdFinding what you need
Check the table above first: most of what people install a package for is already in the standard library. datetime, re, csv, hashlib and json between them cover the large majority of workflow code.
If it is a package for calling a service (requests being the usual one), that work belongs in a step rather than in code; see What to use instead below.
For anything genuinely missing, ask support. The library set is deliberately small so that runs stay reproducible, and additions are considered on request.
What it passes on
Print what you want to pass on. The step captures what your code writes to output. A bare return produces nothing downstream.
import json
print(json.dumps({
"final_total": final_total,
"discount_applied": discount,
}))What you printed sits under result.executionOutput:
{{ 4.result.executionOutput }}Note the result. in the middle. This step nests its output one level
deeper than most, so {{ 4.executionOutput }} finds nothing. Insert the
reference from the data icon rather than typing it and you get the right
path.
executionOutput is text, not an object. You cannot reach into it with
{{ 4.result.executionOutput.final_total }}, because there is nothing to
walk into. To use individual fields downstream, print JSON as above and put a
Parse JSON step after this one.
Print JSON, not Python objects
print(my_dict) produces Pythonβs own formatting: single quotes, True, None. A Parse JSON step cannot read that. Use json.dumps() so the output is valid JSON. Class instances, lambdas and circular references cannot be serialised at all, so convert them to plain values first.
What to use instead
Your code runs on its own: everything it needs arrives through references, and everything it produces leaves through what it prints. Reaching out to the world is the job of the steps around it, not of the code. That keeps credentials out of a code box and every request visible in the run history.
A few things people reach for out of habit therefore do not apply here, and each has a step that does the job better:
| Habit | What happens | Use instead |
|---|---|---|
import requests to call an API | The package is not available, and the network is unreachable regardless | An HTTP Request step before this one to fetch, and one after it to send. It handles authentication, retries and errors for you. |
Sending mail with smtplib | No connection | A Gmail, Outlook or SendGrid step from the dockβs Apps group. |
| Connecting to a database from code | No connection | The database step for that service, which keeps the credentials in your workspace. |
open("out.csv", "w") beside your code | Read-only file system | Write to /tmp for scratch space during the run, then print what matters. To keep a file, pass the contents to a Drive, S3 or email step. |
| Reading a file an earlier run wrote | FileNotFoundError | Every run starts clean. Carry values between runs through the steps themselves, or store them in an app. |
input() to ask for a value | EOFError β nobody is at a keyboard | Reference the value with {{ 2.field }}, or collect it with a Human Review step. |
This is the design, not a gap to work around. A step that fetched its own data would hide that request from the run history, retry nothing when the API is down, and put an API key in a code box. Keeping the reaching-out in dedicated steps is what makes a failed run readable at three in the morning.
Limits
| Limit | Value |
|---|---|
| Run time | 150 seconds |
| Output kept | 100,000 characters |
| Memory | 1.5 GB |
| Network access | None |
Run time. A program that passes 150 seconds is stopped and the step fails. Filter large lists before expensive work, and check that loops terminate.
Output. Past 100,000 characters the result is stored truncated and marked with how many characters were dropped. A step printing a large dataset therefore passes on something that is no longer valid JSON. Print only what the next step needs. For a genuinely large result, print a summary and send the full data onward with a following HTTP Request or an app step such as Google Sheets.
Memory. Beyond 1.5 GB the program stops with a MemoryError. Process records in batches rather than loading everything at once.
Network. Your code cannot open connections, so urllib, socket and anything built on them will not reach a server. Fetch the data with an HTTP Request step before this one and read its result with {{ N.$full_result }}; to send something onward, print it and follow this step with an HTTP Request.
When a run fails
An uncaught exception fails the step and the branch stops. The error message carries what your program printed before it failed, including the traceback, so the stepβs Executions tab tells you which line went wrong.
Guard optional data rather than assuming it is there β the chained .get() on
the highlighted line is what stops a missing field failing the step:
payload = {{ 2.$full_result }}
value = payload.get("response", {}).get("data", {}).get("value", "default")Because a failed run stops the branch, there is no result carrying a flag to test: by the time a later step can read the output at all, the code ran. So the check worth adding on a critical path is not βdid it succeed?β but βis what it printed the shape I expected?β. Where the outcome drives money, deletion or anything else irreversible, follow the step with a Condition that asserts the shape and give the rest an error path.
Examples to copy
Work out a total
import json
order_total = {{ 2.total }}
discount = order_total * 0.1 if order_total > 100 else 0
final_total = order_total - discount
print(json.dumps({
"final_total": round(final_total, 2),
"discount_applied": round(discount, 2),
}))Score and rank a list
A workflow fetches product data and needs weighted scores before writing to a spreadsheet.
import json
products = {{ 3.$full_result }}
scored = []
for product in products:
score = (product["rating"] * 0.6) + (product["reviews"] * 0.004)
scored.append({
"name": product["name"],
"score": round(score, 2),
"tier": "premium" if score > 4.0 else "standard",
})
scored.sort(key=lambda p: p["score"], reverse=True)
print(json.dumps({"ranked_products": scored}))Group and summarise with pandas
import json
import pandas as pd
orders = pd.DataFrame({{ 3.$full_result }})
by_region = (
orders.groupby("region")["amount"]
.agg(["sum", "count"])
.round(2)
.reset_index()
)
print(json.dumps(by_region.to_dict(orient="records")))Follow any of these with a Parse JSON step reading {{ 4.result.executionOutput }}, and the result becomes addressable field by field.
Whatβs Next?
- π Parse JSON β: turn printed JSON into fields that later Steps can select.
- Helper Functions: use a ready-made conversion instead of maintaining code.
- AI Data Transform: reshape unstructured content without code.