ScriptsGuide

Script inputs

The parameters of main() are your script’s inputs. Waiso reads their names, types and defaults straight from the code and draws one field per parameter in the Inputs card — a toggle for a boolean, a select for a fixed set of values, a date picker, a list editor, a JSON editor. The same declaration is what the API, schedules, flows and triggers are checked against, so a wrong value is refused before any worker runs it.

9 min readBefore this: Writing codeOpen in Waiso
What you will learn
  • Declare inputs with types, defaults and optional values in each language
  • Know which field each type becomes in the Inputs card
  • Fill inputs field by field or as one JSON object
  • Read a refused input and fix it

From signature to form

As you type, the editor reads the signature of main and redraws the Inputs card on the left of the code. Python is read on the server with Python’s own parser — the code is parsed, never executed — so Literal, Optional, Annotated, enums and docstrings are understood exactly as Python understands them. TypeScript and JavaScript signatures are read in the browser; C and C++ declare their inputs with // @param lines.

Inputs
Configure the data for this run.
VisualJSON
namestringrequiredtext value
timesintegeroptional1
shoutbooleanoptional
false
toneenumoptionalwarm
tagslist[str]optional
["vip", "newsletter"]
Fill name to run
greet.py
1from typing import Literal2 3def main(4    name: str,5    times: int = 1,6    shout: bool = False,7    tone: Literal["warm", "formal"] = "warm",8    tags: list[str] = [],9):10    word = "Hello" if tone == "warm" else "Good day"11    text = f"{word}, {name}!"12    if shout:13        text = text.upper()14    return {"greetings": [text] * times, "tags": tags}
Python 3.15LSP
Each parameter is one field, with its type chip and a required or optional mark.

Every field shows a small chip with its type (string, integer, boolean, enum, date, list, json…) and the word required or optional. Under the card, a line says Ready to run · 3 inputs, or Fill customer to run while a required field is empty — Run stays disabled until it is filled. A script with no parameters has no card at all.

Declare typed inputs

One script, six kinds of input. Paste it into a new script and watch the card: a required text field, a number, a toggle, a select with three options, an optional date and a list editor.

order_summary.py
from datetime import date
from typing import Annotated, Literal


def main(
    customer: Annotated[str, "Who placed the order"],
    quantity: int = 1,
    express: bool = False,
    region: Literal["br", "us", "eu"] = "br",
    ship_on: date | None = None,
    tags: list[str] = [],
):
    return {
        "customer": customer,
        "quantity": quantity,
        "express": express,
        "region": region,
        "shipOn": ship_on,
        "tags": tags,
    }
Expected outputSuccessThe Python variant run with customer, quantity, express, ship_on and one tag filled in
{
  "customer": "Ana Souza",
  "quantity": 2,
  "express": true,
  "region": "br",
  "shipOn": "2026-11-03",
  "tags": [
    "gift"
  ]
}

Required, optional and nullable

  • A parameter with a default is optional; the field starts filled with that default.
  • A parameter without a default is required — unless its type allows nothing: X | None, Optional[X] or a default of None in Python, T | null or T | undefined in TypeScript, or a ? on the name.
  • An optional field left empty sends the code’s default; a nullable one with no default sends null (None in Python).
  • In Python, a default that is a module-level constant is resolved; a default that is a call (datetime.now()) still makes the input optional, but the field starts empty.

Descriptions (Python)

A Python input can carry a description, shown under its field. Write it in main’s docstring — Google (Args:), Sphinx (:param name:) and NumPy styles are read — or as Annotated[str, "text"]. When both exist, the docstring wins. Comments are never read, and the other languages have no descriptions.

invoice.py
def main(amount: float, currency: str = "BRL"):
    """Format an invoice line.

    Args:
        amount: Total in the currency's main unit, e.g. 129.90
        currency: Three-letter ISO code
    """
    return f"{currency} {amount:.2f}"

Which field each type becomes

FieldPythonTypeScriptC / C++ (@param)
Textstrstringstring
Number (whole)intbigintint
Numberfloat, Decimalnumberfloat, double
Toggleboolbooleanbool
SelectLiteral["a", "b"] or an Enum class'a' | 'b'—
Datedate——
Date and timedatetimeDate—
List editorlist[X], tuple, setX[], Array<X>list
JSON editordict, Any, a TypedDict or dataclassRecord<…>, { … }, objectjson

With no type, the default decides: = 3 is a whole number, = true a toggle, = [] a list. That is how JavaScript inputs are typed. The list editor types each row by the item type — list[int] rows are numbers, list[bool] rows are toggles — and has Add item, Move up, Move down and Remove item; an empty list says what it sends (Empty list — sends []).

Visual or JSON

The Visual / JSON switch at the top of the card changes how you fill it, not what is sent. JSON shows every input as one object — handy to paste a payload you received from an API, or to copy what a run used. Switching back to Visual reads the object into the fields; if it is not valid you see The JSON is not valid: … or expected a JSON object.

Expected outputThe card in JSON mode: one key per input
{
  "customer": "Ana Souza",
  "quantity": 2,
  "express": true,
  "region": "br",
  "ship_on": null,
  "tags": [
    "gift"
  ]
}

To reuse the inputs of an earlier run, open it in Run history and pick Restore inputs: the values that still match the current parameters are copied into the card.

How values are checked

Every way of starting a run — Run in the editor, an API call, a schedule, a flow step, an MCP client — passes the same check against the declared inputs before anything is queued. The check is forgiving where it can be: numeric text is accepted for a number, "true" / "False" for a toggle, "None" or an empty text for a nullable input, 5.0 for a whole number. Dates must be YYYY-MM-DD; a date and time accepts a T or a space and an optional zone. A missing optional value lets the code’s default apply.

When a value cannot be used, the run is refused with HTTP 422 and the code invalid_parameter — and nothing is sent to a worker. In the editor the field turns red with Expects integer · received "2.5", and the Output tab shows Invalid input with Show the field. Editing the field clears the mark. For Python, keys that name no parameter are dropped (unless main takes **kwargs), and a missing required input is refused as received nothing.

Expected outputSuccessThe 422 body an API caller gets for the same mistake
{
  "error": "Parameter \"quantity\" expects integer, received 2.5",
  "code": "invalid_parameter",
  "parameter": "quantity",
  "expected": "integer",
  "received": "2.5"
}
After a valid run, the Output tab shows what main returned.

Limits

  • Python code up to 512 KB is read for inputs; reading takes at most a second. If it cannot be read, a simpler built-in reader takes over — a run is never refused for that.
  • *args and **kwargs never become fields.
  • If a file has several top-level def main, the last one is used.
  • C and C++ inputs exist only through // @param lines; no line, no field.