Custom Function
The escape hatch: run Python when no node does quite what you need. The function signature, how data gets in and out, what you may import, and why reaching for this too early is a mistake.
Sooner or later a workflow needs something the palette does not have. Reformat a phone number into international form. Work out which of five price bands a deal falls into. Turn a supplier's oddly shaped JSON into the fields your CRM expects.
Custom Function is the escape hatch: a node that runs Python you write, in the middle of a workflow, with the data flowing through it.
Reach for it last, not first. Code is the part of a workflow that other people cannot read, the builder cannot validate, and nobody will want to change in a year. Check that Transform Data, a Condition or a Set Variable cannot do it before you write a function โ most of the time one of them can.
The shape of the code
Define a function called process that takes inputs and context, and return whatever the next node should receive. What you return lands in {{alias.output}}.
def process(inputs, context):
raw = str(inputs.get("raw_phone") or "")
digits = "".join(ch for ch in raw if ch.isdigit())
if len(digits) == 10:
digits = "91" + digits
return {"e164": "+" + digits if digits else "", "digits": len(digits)}Returning a dictionary rather than a bare value is worth doing by habit. A dict gives every piece of what you computed its own name, so the next node reads output.e164 instead of an unlabelled output that nobody can interpret six months later.
Getting data in
The function does not automatically see the workflow. You declare what it needs in input_sources โ a map of variable name to template โ and read them from inputs.
| Field | What it holds |
|---|---|
code | Python code to execute |
input_sources | Structured input sources mapping - maps variable names to template expressions |
timeout | Execution timeout in seconds Defaults to 60. |
capture_output | Capture stdout/stderr output Defaults to true. |
code
- What it holds
- Python code to execute
input_sources
- What it holds
- Structured input sources mapping - maps variable names to template expressions
timeout
- What it holds
- Execution timeout in seconds Defaults to
60.
capture_output
- What it holds
- Capture stdout/stderr output Defaults to
true.
A few lines of Python in the middle of a workflow
Click the function node to see what it is given and how long it may take.
Scroll for all 3 steps โ
Declaring inputs explicitly is more typing than a function that reaches for anything it likes, and it is the reason this node is debuggable. The panel states exactly what the function depends on, so a reference that breaks upstream is visible here rather than inside the code.
Watch out: Escape hatch: define process(inputs, context); read an upstream node via inputs.get("<alias>").
What you can import
The Requirements field does not install anything. It looks like a package list and it is a hint โ it feeds the UI's picker and the AI code generator. Nothing is pip-installed when the workflow runs, so only libraries already present in the worker image can be imported. Anything else raises ImportError at run time, not when you save. Verified in the node source, 2026-08-01.
In practice this is less limiting than it sounds โ the standard library is there, and the things a workflow usually wants (json, re, datetime, math) are all standard. But test an import before you build a workflow around it.
Time, and failure
| Field | Behaviour |
|---|---|
timeout | Seconds before the function is killed. 60 by default. A function that calls an external service should set this deliberately rather than inherit it. |
capture_output | On by default. Anything you print is captured and shows in the execution log โ which is the main way to debug a function that returns the wrong thing. |
timeout
- Behaviour
- Seconds before the function is killed. 60 by default. A function that calls an external service should set this deliberately rather than inherit it.
capture_output
- Behaviour
- On by default. Anything you
printis captured and shows in the execution log โ which is the main way to debug a function that returns the wrong thing.
When a function raises, the node fails and the run stops there. That is usually what you want. If the function is doing something optional โ enriching a record that may not have the data โ catch the exception inside and return a sentinel your next node can branch on, rather than letting one missing field kill the run.
When it is the right answer
| Job | Better tool |
|---|---|
| Pull a value out of a nested response | transform_data |
| Compare something and branch | condition |
| Hold a value for later steps | variable_set |
| Fetch something from an API | api_request |
| Reshape, compute or validate in a way none of the above express | custom_function |
Pull a value out of a nested response
- Better tool
transform_data
Compare something and branch
- Better tool
condition
Hold a value for later steps
- Better tool
variable_set
Fetch something from an API
- Better tool
api_request
Reshape, compute or validate in a way none of the above express
- Better tool
- custom_function
The good uses share a shape: a small, pure transformation with a name you could say out loud โ normalise a phone number, score a lead, map their statuses onto ours. When a function starts fetching things and updating records, those parts belong in nodes around it, where the workflow can show what is happening.
Try it
- Add a Custom Function and give it one input in
input_sourcespointing at a field from your trigger. - Return
{"seen": inputs.get("your_field")}and run it, then read{{customfunction_1.output.seen}}in a node downstream. - Add a
print()and find it in the execution log. - Now
import requestsat the top and run it again โ if it raisesImportError, you have just proved the Requirements field to yourself.
Next: Human Approval โ for the step that should not happen until a person says so.
Related lessons
Base rates โ what a piece of evidence is actually worth
A face-recognition system that is 99.9% accurate and almost entirely wrong, and a number that sent an innocent woman to prison. Both are the same arithmetic, and it is the arithmetic that decides what any piece of evidence is worth.
ReadConfirmation and survivorship โ what you never looked for
Two questions about evidence you did not go looking for. One is a rule you have to discover, and one is a pattern in five famous people โ and in both, the thing that would have told you the truth is the thing nobody checks.
ReadLoss aversion, sunk cost and regression โ what it costs you
Four questions you answer about yourself rather than about a scenario, and your own answers are the finding. Then the pattern that makes praise look useless and criticism look like it works, whatever you actually do.
Read
