Docs / The language & scripts
Reading what triggered you
A run starts because something happened. input is where that something is,
and it is the only global carrying data. It has exactly two parts:
input.event— what triggered this run.input.settings— the value your linked settings script returned, so the values that differ between sandbox and production are never in your code.
input.event, door by door
Six fields are always there — id, kind, tenantId, environmentId, body
and receivedAt — and kind is what you branch on when one script serves more
than one door.
kind | What happened | What body holds |
|---|---|---|
webhook | A call arrived on one of your endpoints. The caller was answered immediately, before your script ran. | The request body — parsed when it is JSON, the raw text otherwise. Plus headers and endpoint. |
api_service | A call arrived on a synchronous endpoint and the caller is waiting for this run’s answer. Use respond(value) — see Answering a caller. | The same as a webhook. |
scheduler | A schedule came due. Nobody called you; there is no payload from outside. | { schedule, cron, firedAt }. |
email_in | A message arrived in a mailbox this environment watches. One run per message. | The envelope: sender, recipients, subject, body, attachment descriptions. |
system | Something in this environment went wrong — a run failed, work could not be delivered, something was marked as failing. | The occurrence: what broke and where, never the payload that caused it. See Reacting to failures. |
manual | Somebody ran the script on demand — the Test button, or the command line. | Whatever input they supplied. |
Reading it is as ordinary as it looks:
const order = input.event.body;
log("order arrived", {
id: order.id,
from: input.event.headers["x-source"],
at: input.event.receivedAt,
});
return order.customer;
receivedAt is when the trigger arrived, which is not the same as now: a
scheduled run that was caught up late, or a run that waited on a busy system,
starts well after it. Both moments are available and they answer different
questions.
One script, several doors
A script with more than one trigger asks which one it is:
if (input.event.kind === "scheduler") {
return "nightly sweep for " + input.event.body.schedule;
}
if (input.event.kind === "webhook") {
return "order " + input.event.body.id;
}
return "not a door this script handles";
Often you do not need this. A webhook trigger can say which events it wants — a filter on the payload — so two small scripts can take disjoint slices of one endpoint’s traffic instead of one script starting with a dispatch table. An event no trigger wants is accepted and counted, and starts nothing.
input is read-only, all the way down
Assigning to any part of it throws — catchably:
try {
input.event.body.status = "processed";
} catch (error) {
return error.name; // "TypeError"
}
This is not tidiness. The trigger is the run’s evidence of what it was asked to do, and a run that is continued somewhere else must read exactly what it started with. Build a new value instead:
const order = input.event.body;
const enriched = {
id: order.id,
customer: order.customer,
status: "processed",
itemCount: order.items.length,
};
return enriched;
For a deep copy of something big, JSON.parse(JSON.stringify(input.event.body))
is the whole trick.
input.settings — configuration as data
A settings script is a script whose returned value becomes input.settings
for every main script that links it. It is where anything environment-specific
belongs: which connection to use, a page size, a mailbox, an account number that
differs between sandbox and production.
const settings = input.settings;
const response = fetch({
connection: settings.crmConnection,
method: "GET",
path: "/contacts",
query: { pageSize: settings.pageSize },
});
return response.status;
A settings script has to produce a value
The settings script itself is written like any other script, and the one thing
it owes is a result. There are two ways to give one, and they are the same
two every script has: assign output, or use a top-level return.
output = {
crmConnection: "CRM",
pageSize: 100,
};
return { crmConnection: "CRM", pageSize: 100 };
The trap is the version that reads best and answers nothing:
// This script runs, finishes, and hands its scripts NOTHING.
const settings = { crmConnection: "CRM", pageSize: 100 };
Nothing is wrong with it, and nothing comes out of it: settings is a local
name that goes away when the script ends. The editor marks a settings script
that can produce no value, and the note above it says the same thing — but the
mark is a warning, not a refusal, so it never stops you saving.
With no settings script linked, input.settings is undefined — and reading a
field of it throws immediately, which is the honest failure: a script that needs
settings and was not given any should stop at its first line, not halfway
through its work.
Secrets do not go here. A settings value is ordinary data that anyone reading the script’s run can see. Credentials belong on a connection, where the platform applies them without a script ever holding one.
Trying it before it is real
Every trigger shape can be tested without waiting for the real thing: the run
panel’s Test input box becomes input.event.body. For a system trigger
the panel offers a sample payload of each occurrence, so a handler can be
written and proven before anything has actually broken.