Flows
Auto-CRUD covers most reads and writes. When you need a custom HTTP endpoint — multi-step logic, an outbound API call, a computed response — you write a flow in flows.yaml. A flow is a sequence of steps that runs server-side and returns whatever you tell it to.
The shape
A flow file has two top-level keys: on: (the trigger — method and path) and jobs: (the named job whose steps run in order). The job's map key is the flow's name.
on:
request:
method: POST
path: /api/posts/:id/publish
auth: required # default is anonymous; require a session here
jobs:
publish:
steps:
- id: post
run: sql
with:
query: "UPDATE posts SET status='published' WHERE id = :id RETURNING id, status"
params: { id: "$" }
- run: respond
with:
status: 200
json:
id: "$"
status: "$"
One on: block and one jobs: map per file. For several flows, write several files under flows/*.yaml — each file is one trigger and one job.
Step types
| Step | Does |
|---|---|
sql | Parameter-bound query with :name placeholders — no injection risk. |
api | Outbound HTTP request, with optional signing for authenticated APIs. |
parallel | Fan out child steps concurrently. |
if | Conditional branch; gated steps nest under it. |
for_each | Iterate over a list. |
email / webhook | Send a transactional email or an outbound POST. |
enqueue | Push a background job. |
parse | Extract JSON, form fields, or headers from the request. |
respond / redirect | Terminal response — a status with JSON/body, or a 302. |
Passing data between steps
References use $. Read request input with $ (query, path, or body), the session user with $ (requires auth: required), and a prior step's result with $.
- For an
INSERT/UPDATE/DELETEto expose values downstream, addRETURNING <col>. steps.x.outputs.first.colis the first row;steps.x.outputs.rowsis the whole set.- Provide a fallback for optional input:
$standard. - An unresolved reference halts the flow with a clear error listing what was missing — it never leaks the raw placeholder into the response.
Public and authenticated routes
A flow is public unless you set auth: required on on.request. Request parameters are bound before any auth check, so a public endpoint like GET /api/share/:token can read $ with no session. To call an authenticated flow from the frontend, use the generated client: bm.flows.publish({ id }).
Long-running flows
If a flow fans out to slow upstream services, add mode: async to on.request. The request returns 202 immediately with a job_id and a status_url; a background worker runs the steps. Poll the returned status_url verbatim (it carries a per-job token) until the job completes.
Declaring inputs
An optional inputs: block on on.request documents and types the flow's parameters, which in turn types the generated bm.flows.<name>() call and can drive an auto-generated input form.