Functions
A function is a named, parameterised sequence of steps defined inside one workflow and run by the Call Function activity.
It is not a separate workflow. The body runs as a frame in the same run, sharing the same browser and desktop session — so a function that clicks through a portal continues from the page the caller left open, and its steps appear in the same run's logs.
That is the difference worth holding on to:
| Runs in | Session | Shows up as | |
|---|---|---|---|
| Function | the caller's run | the caller's | steps in the same run |
| Execute Child Workflow | a separate run | its own | a separate execution |
Reach for a function when the same six steps appear three times, when a block has to be called from several branches, or when the work is genuinely recursive — recursion is allowed, and bounded at 32 frames.
Defining one
The Functions section at the bottom of the designer sidebar lists the workflow's functions, each with its parameter count. + creates one, opens its body on the canvas and opens Configure Function, which is where the signature lives:
- Name — what call sites reference. Unique within the workflow.
- Description
- Parameters — each with a type, an optional default, and a required flag.
- Returns — each with a name and a type.
The body is an ordinary canvas. Click a function in the list to open it; the split icon opens it beside the main flow, which is the useful one while wiring up a call.
Deleting asks first, and tells you how many activities call it — those call sites are left pointing at a function that no longer exists.
Calling one
Add Call Function, pick the function, then map two things: each argument to
a value or {{ expression }}, and each declared return to a caller variable.
Omitted optional parameters use their defaults.
What the body can see
This is the part that surprises people, so it is worth stating plainly.
A function frame is seeded with the workflow's global variables and the arguments — and nothing else.
Caller locals are invisible. A variable an activity produced in the main flow is not in scope inside the body. If the body needs it, pass it as an argument. This is deliberate: it is what lets a function be called from three places without three different sets of assumptions about what happens to be lying around.
Globals arrive as their live values, not the values declared on the workflow, so a global reassigned earlier in the run arrives as it now stands.
A parameter that shares a global's name shadows it inside the body, and the argument it carried does not leak back into the global afterwards.
What comes back
On success:
- Each declared return binds to the caller variable named in the call.
- Writes to globals are merged back, so a body that updates a running total updates it for the caller too.
- A return the body never set binds null and logs a warning, rather than failing the call.
On failure, nothing is written to the caller — no returns, no globals. A half-finished function leaves the caller's scope as it found it.
Failures and flow control
- A failure inside the body propagates to the call site. Wrap the call in
TryCatchto handle it there. breakandcontinuedo not escape a function. A stray one ends the body without touching a loop in the caller.- End Execution inside a body ends the whole run, including its failing
form — it is not swallowed by a
TryCatcharound the call. - Exceeding 32 frames fails that Call Function with the call stack in the message, rather than overflowing.
Types
An argument whose type differs from the declared parameter type is coerced to
the declared type for that call, and the run logs a warning saying so — the
string "100" passed to an integer parameter arrives as 100. An argument
that cannot be coerced fails the call, and so does a missing required argument.
In the DSL
The Code tab shows a function as a def block:
def fetchOrder(reference: string, retries: integer = 3) -> (total: float, status: string):
StartWebBrowser("https://portal.example.com/orders")
TextInput("//input[@name='ref']", "{{ reference }}")
total = GetHtmlElementProperty("//span[@class='total']", "textContent")
and a call as an ordinary activity:
orderTotal = CallFunction("fetchOrder", arguments={"reference": "{{ row.ref }}"})
The assignment names one return. A function that declares several maps the rest in the call's own outputs, which is what the configure panel edits.
Availability
The Functions section appears only when the engine serves Call Function. If the section is not in your sidebar, this deployment does not have it yet.