Docs Services Sign In
Backend logic

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.

flows.yaml
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

StepDoes
sqlParameter-bound query with :name placeholders — no injection risk.
apiOutbound HTTP request, with optional signing for authenticated APIs.
parallelFan out child steps concurrently.
ifConditional branch; gated steps nest under it.
for_eachIterate over a list.
email / webhookSend a transactional email or an outbound POST.
enqueuePush a background job.
parseExtract JSON, form fields, or headers from the request.
respond / redirectTerminal 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 $.

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.

Related: Hooks for side effects on ordinary CRUD, and Workflows for state machines.