A workflow is a small automation attached to one of your tables. Every workflow can be read as a single sentence: WHEN something happens, IF the data matches, DO these steps in order. You describe that sentence as JSON, and the platform runs it for you every time the event happens - no server code to deploy.
Before writing any JSON, say your automation out loud. For example: "When an order is created, if the total is 500 or more, mark it for review - and if the customer is VIP, also make it high priority." Each part of that sentence maps to exactly one part of a workflow.
The event that starts the workflow: a record is about to be / was created, updated, or deleted; someone calls it by name (manual); or a schedule fires (cron).
Optional checks on the data, such as record.total >= 500. All must be true or the workflow is quietly skipped. A skip is normal, not an error.
An ordered list of small actions: change the record, read or write other tables, branch, loop, calculate, call an API, notify someone.
Each named step saves its result so later steps can use it. A manual workflow can also send a response back to the caller with a return step.
// WHEN a new order is about to be saved...
function beforeCreateOrder(order: NewOrder): NewOrder {
// IF the order is large enough to need a human look...
if (order.total < 500) return order;
// DO step 1: mark it for review
order.status = "needs_review";
order.reviewRequestedAt = new Date();
// DO step 2 (only for VIP customers): raise the priority
if (order.customerTier === "vip") {
order.priority = "high";
}
return order; // saved together with the changes above
}trigger = WHEN, conditions = IF, steps = DO. Notice the second step has its own condition: workflow-level conditions decide whether anything runs, step-level conditions decide whether that one step runs.
This is the exact order the engine follows every time. When something does not behave as expected, walk through these six checkpoints - the answer is almost always at one of them.
A record is created, updated, or deleted in the table; an app calls the workflow by name; or the cron schedule ticks.
Out of all workflows on the table, only those that are active (isActive: true) and whose trigger equals the event are considered. Everything else is ignored.
Every condition in the top-level conditions list must be true. If any is false, the workflow is skipped and the original operation continues normally.
Steps are sorted by their order number (10, 20, 30...). Inactive steps and steps whose own conditions are false are skipped. The rest run one at a time.
After a step named load-product finishes, later steps can read its result as steps.load-product - for example {{steps.load-product.price}}.
It ends after the last step, early at a return step, or at the first error when stopOnError is true. For transactional work an error rolls back everything, including the original save.
Steps and conditions read data with dotted paths. Inside a step config, wrap the path in double braces, like {{record.total}}, and it is replaced with the real value at run time. Inside a condition field, write the path without braces, like record.total.
The record being created, updated, or deleted - or, for a manual call, the JSON body you sent. Example: {{record.customer}}.
The record as it was before an update. Use it to compare old and new values, or use the changed / changed_to / changed_from operators.
Results of earlier named steps in this run. Example: {{steps.load-product.stock}}.
vars holds values you stored with set_variable, transform, or equation. Inside a for_each, loop.item (or your itemName) is the current element and loop.index its position.
For manual calls, the URL query parameters (except schemaName). Example: ?limit=10 becomes {{query.limit}}.
{{now}} is the current UTC time, {{today}} is today at midnight UTC, {{user.id}} is the signed-in user, {{schemaName}} and {{workflowName}} describe the run.
Most beginner surprises come from these two choices. Pick the timing (before or after) from what you need to do with the record, then pick the mode from whether the work must succeed or fail together with the save.
Run before the record is written. Use them to fill in defaults, clean up values, or block the save with a fail step. Changes made with set_record are saved as part of the record.
Run once the record exists. Use them to create related records, update other tables, write audit logs, or hand work to the background.
Steps run immediately inside the same database transaction as the save. If a step fails, the record and every change made by the workflow are rolled back together.
Steps are queued and run after the save is committed. Required for things that cannot be undone: call_api, execute_dynamic_api, invalidate_cache, create_notification. Lifecycle outbox work is queued from after_* triggers.
Set mode to hybrid when one workflow needs both: each step then declares executionMode transactional or outbox. Typical pattern: reserve stock transactionally, then send the webhook through the outbox.
You do not need to read everything. Follow this path and stop once you can build what you need.
Learn what every top-level field and step field means, and the validation rules that reject a definition before it runs.
Build, test, and extend a one-step workflow. Takes about five minutes.
Decide when your workflow should run and whether its work belongs in the transaction or the outbox.
Templates, conditions, branches, loops, variables, and step outputs - the tools for real business rules.
Look up any step type, then copy a complete tutorial or recipe that is closest to your use case.