Cookbook
Recipes that compose operations to solve common tasks. Each one is a complete request body you can send to the endpoint. If you're new to the operation language, read Operations first. Each recipe links to the operations it uses.
The one rule to remember
A parameter that's an array is evaluated as an operation; anything else is taken
literally. So a scalar passes straight through, but to pass an array or object as
data you have to wrap it in value, or it's read as an operation and
rejected.
["run", "getUser", 1]
["run", "createUser", ["value", {"email": "ada@example.com", "roles": ["admin"]}]]
The first passes the literal 1. The second wraps the object so it's passed as data
instead of being read as an operation.
Reading and reshaping data
Pick one field from a result
Run an action and pull a single field out of its result with get.
["get", "email", ["run", "getUser", 1]]
"ada@example.com"
Reach into nested data
get keys support dot paths, so you can reach deep into a result in one step.
["get", "address.city", ["run", "getUser", 1]]
"Berlin"
Provide a fallback when a field is missing
A third parameter to get is the default, returned when the key is absent (it'd
otherwise be null).
["get", "nickname", ["run", "getUser", 1], ["value", "Anonymous"]]
Build a custom response object
object evaluates each value, so you can assemble the shape you
want, pulling from several actions in a single request.
["object", {
"user": ["run", "getUser", 1],
"posts": ["run", "getPosts", 1]
}]
{
"user": { "id": 1, "username": "ada" },
"posts": [ { "id": 10, "title": "Hello" } ]
}
Lists and collections
Turn a list into a lookup map
map takes a key field and a value field and builds a {key: value}
object from a list.
["map", "id", "username", ["run", "getUsers", ["value", {"role": "member"}]]]
{ "1": "ada", "2": "linus" }
Take the top N
Sort a result, then take from the front. sort with a field and
direction, then take for how many.
["take", 3, ["sort", "score", "desc", ["run", "getPlayers", "season-2024"]]]
Take the last N
A negative count for take counts from the end.
["take", -5, ["run", "getEvents", "today"]]
Sort a plain array
sort with just a direction sorts a flat array. The array literal is wrapped in
value.
["sort", "asc", ["value", [5, 2, 8, 1]]]
[1, 2, 5, 8]
Apply defaults to a result
merge shallow-merges arrays left to right, so later values win. Put
defaults first and the real result second to fill in only what's missing.
["merge",
["value", {"role": "guest", "active": true}],
["run", "getUser", 1]
]
Logic and conditionals
Branch on a comparison
ifElse picks one of two expressions based on a check. Here the
check is a compare. All three branches have to be expressions, so
the literals are wrapped in value.
["ifElse",
["compare", ["get", "age", ["run", "getUser", 1]], ">=", ["value", 18]],
["value", "adult"],
["value", "minor"]
]
Combine several checks
and (and or) evaluate to a boolean and short-circuit.
["and", ["run", "isActive", 1], ["run", "isAdmin", 1]]
true
First value that isn't null
coalesce returns the first non-null result and stops. Unlike a
get default, the alternatives can be whole actions, which is handy for a cache
fallback.
["coalesce",
["run", "getFromCache", "user:1"],
["run", "getUser", 1]
]
Multi-step requests
These use sequence (evaluate each item, return the last) and
var (store and recall a value) to do several things in one request.
Fetch once, use many times
Calling an action twice does the work twice. Store it in a variable instead and read it back as often as you like.
["sequence",
["var", "user", ["run", "getUser", 1]],
["object", {
"name": ["get", "username", ["var", "user"]],
"email": ["get", "email", ["var", "user"]]
}]
]
Create something, then use its id
The result of a create flows into the next step through a variable.
["sequence",
["var", "user", ["run", "createUser", ["value", {"email": "ada@example.com"}]]],
["run", "sendWelcome", ["get", "user_id", ["var", "user"]]]
]
Read a nested value from a stored variable
var reads support the same dot paths as get.
["sequence",
["var", "user", ["run", "getUser", 1]],
["var", "user.address.city"]
]
Converting types
cast converts a value to int, float, bool, or string, which
is useful when an action returns a number as a string.
["cast", "int", ["get", "count", ["run", "getStats", "today"]]]
Paginating
list runs a list action and returns { "total", "result" }. Its
data is wrapped in value and has to include properties.
["list", "users", ["value", {
"properties": ["id", "username"],
"filters": {"role": "admin"},
"page": 0,
"pageSize": 20
}]]
{
"total": 42,
"result": [
{ "id": 1, "username": "ada" },
{ "id": 2, "username": "linus" }
]
}
Putting it together
A single request can do all of the above at once. This fetches a user, then builds a dashboard object (their name, their three most-liked posts, and whether they're an admin), reusing the stored user along the way.
["sequence",
["var", "user", ["run", "getUser", 1]],
["object", {
"name": ["get", "username", ["var", "user"]],
"topPosts": ["take", 3, ["sort", "likes", "desc", ["run", "getPosts", 1]]],
"isAdmin": ["compare", ["get", "role", ["var", "user"]], "=", ["value", "admin"]]
}]
]