Workflow format
How a workflow's trigger and steps are written as JSON, for reading the program that Get a workflow returns.
Get a workflow returns a workflow's trigger and steps as program: a tree of JSON nodes, each naming what it is with a kind. You never need to write one: the editor and your AI tool do that. This page explains how to read one.
A program
{
"kind": "Program",
"version": 1,
"inputs": [],
"body": []
}body is the list of statements, run in order. When the workflow starts from a trigger or a schedule, that's the first statement. inputs holds the inputs it declares, and version is the version of this format, currently 1.
An example
The Stripe example from these docs, as a program: a trigger, then two app actions that read the payment.
{
"kind": "Program",
"version": 1,
"body": [
{
"kind": "LetStmt",
"name": "payment",
"init": {
"kind": "NativeCallExpr",
"name": "stripe_new_payment_trigger",
"args": [{ "kind": "Literal", "value": "succeeded" }]
}
},
{
"kind": "LetStmt",
"name": "logged",
"init": {
"kind": "NativeCallExpr",
"name": "api.request",
"args": [
{ "kind": "Literal", "value": "POST" },
{ "kind": "Literal", "value": "/actions/library/google-sheets/append-row" },
{
"kind": "Literal",
"value": {
"spreadsheet_url": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZ",
"sheet_name": "Revenue",
"row_values": [
{ "kind": "NowBuiltIn" },
{ "kind": "MemberExpr", "obj": { "kind": "VarRef", "name": "payment" }, "key": "description" },
{ "kind": "MemberExpr", "obj": { "kind": "VarRef", "name": "payment" }, "key": "amount" }
]
}
}
]
}
},
{
"kind": "LetStmt",
"name": "posted",
"init": {
"kind": "NativeCallExpr",
"name": "api.request",
"args": [
{ "kind": "Literal", "value": "POST" },
{ "kind": "Literal", "value": "/actions/library/slack/send-message" },
{
"kind": "Literal",
"value": {
"text": {
"kind": "BinaryExpr",
"op": "+",
"left": { "kind": "Literal", "value": "New payment: " },
"right": { "kind": "MemberExpr", "obj": { "kind": "VarRef", "name": "payment" }, "key": "description" }
}
}
}
]
}
}
]
}Each step's result is bound to a name with a LetStmt: the trigger's event is payment, which later steps read with MemberExpr nodes. In the editor, those reads are the @payment.description references.
Statements
There are 14 kinds of statement:
kind | Fields | In the editor |
|---|---|---|
LetStmt | name, init? | A step whose result has a name, such as an action or a calculation |
AssignStmt | name, value | Changing a value declared earlier |
ExprStmt | expr | A step whose result isn't kept |
BlockStmt | body | A group of statements |
IfStmt | cond, thenBranch, elseBranch? | If, with Otherwise as elseBranch. An elseBranch that is itself an IfStmt makes a chain of cases. |
ForOfStmt | name, iterable, body | Repeat for each item. name is the current item. |
RepeatStmt | count, body | Repeat N times |
WhileStmt | cond, body | Repeat while |
BreakStmt, ContinueStmt | Leave a loop, or skip to its next pass | |
TryStmt | tryBranch, catchBranch, errorName? | A try section; see below |
StopStmt | status, message? | End the run early, with status success or failure |
WhenStmt | time, recurrence?, timezone? | A schedule at a time of day; see below |
FrequencyStmt | value, unit | A schedule every value units (seconds, minutes, hours or days) |
Try sections
A TryStmt runs tryBranch. If a step inside it fails, the rest of tryBranch is skipped and catchBranch runs instead, with the failure bound under errorName (error unless set) as { "type": …, "message": … }. The type is action, http, timeout, variable or error. An empty catchBranch lets the run carry on after the failure. A StopStmt inside a try still ends the run. See Handle failures.
Schedules
A WhenStmt runs the workflow at time:
- With
recurrence,timeis a time of day ("08:00") andrecurrenceis an iCalendar rule without theRRULE:prefix, such as"FREQ=WEEKLY;BYDAY=MO,WE,FR"or"FREQ=MONTHLY;BYMONTHDAY=1". - Without it,
timeis the next occurrence of a time of day, or an ISO date and time for a single run. timezoneis an IANA name such as"America/New_York". Without one, the schedule follows the workflow owner's time zone.
Expressions
Expressions produce values:
kind | Fields | Produces |
|---|---|---|
Literal | value | A fixed value: text, number, boolean, null, list or object |
VarRef | name | A named value: a step's result, the current item of a loop, or an input |
MemberExpr | obj, key | A field of an object, such as payment.amount |
IndexExpr | arr, idx | An item of a list by position, from 0; -1 is the last. A position past the end gives null. |
UnaryExpr | op, right | - (negate), +, or ! (not) |
BinaryExpr | op, left, right | + - * / % **, comparisons == != < <= > >=, and && ||. + joins text when either side is text. |
NativeCallExpr | name, args | An app action or a trigger; see Calls |
| Built-in function | its own fields | See Built-in functions |
Calls
App actions
An app action is a NativeCallExpr named api.request with three arguments: the method, the action's path, and a payload of its inputs. The path names the app and the action: /actions/library/google-sheets/append-row is Google Sheets' Append row. The keys of the payload are the action's inputs, as listed on its integration page.
In the payload, a fixed value is written as plain JSON, and a value worked out at run time is an expression node in its place, like row_values and text above. Any object in the payload with a string kind is evaluated, at any depth.
Lodol's own Request approval and stored-value steps are calls too, to /actions/core/review/request-approval and /actions/core/variables/get, set, increment and delete.
Triggers
A trigger is a NativeCallExpr named with the trigger's ID, such as stripe_new_payment_trigger, in the first statement. Its args are its filters, in order. The LetStmt around it names the event.
human.* and web.* calls don't run
Workflows written against older versions of this page may contain human.* or web.* calls. They do nothing when a workflow runs. To wait for a person, use the Request approval step.
Built-in functions
A built-in function is a node whose kind ends in BuiltIn, with named fields that are themselves expressions. Each one is a built-in step in the editor.