Skip to main content
Workflows are called drawers in Buttons. A drawer chains one or more buttons into a repeatable run with typed inputs, step outputs, validation, and history. Use a button for one action. Use a drawer when the action has more than one step, needs data from an earlier step, or should be triggered as a unit.

Mental model

Each drawer lives on disk as drawer.json under the Buttons drawers directory and records its own run history under pressed/.

Create a drawer

add accepts button names. It also accepts drawer/<name> for sub-drawers, for_each:<button> for loops, and wait:<duration> for time pauses.

Wire data between steps

When a button prints valid JSON to stdout, the drawer parses it and exposes it as the step output.
If stdout is not JSON, the raw stdout string is still recorded in history, but structured refs like ${step.output.field} only work against JSON output.

Auto-connect by schema

If a button declares output_schema, buttons drawer <name> connect A to B can match compatible output fields to downstream args.
Use explicit wiring when the field names do not line up:

Drawer inputs

Any unfilled required button arg can be supplied at drawer press time by name.
Explicit step args win. If a step arg is not set and a drawer input with the same name exists, Buttons fills it for that step.

Run modes

By default a drawer runs its steps sequentially. Pass --mode parallel to run steps concurrently while honoring data dependencies — a step waits only for the steps its ${...} refs actually name. --concurrency N caps how many run at once (0 means NumCPU), and --on-failure stop|continue controls whether the first failure cancels in-flight siblings.

Step kinds

Sub-drawers

A drawer can call another drawer with drawer/<name>.
Sub-drawers are useful when a workflow has a reusable middle section. The child drawer can define a return block in drawer.json so the parent can reference selected values as ${child-build.output.<field>}.

Loops

Use for_each:<button> for per-item work.
Useful loop fields: The loop output includes results in input order, so downstream refs stay deterministic even when iterations run concurrently.

Switches

Switch steps run the first case whose when expression is truthy. Author them in drawer.json.
The top-level steps on a switch act as the default branch.

Aggregates

Aggregate steps collect values from an array, often after a for_each.
The output is { "values": [...], "count": N }, with values in the same order as the input.

Waits

Use waits to pause between steps.
You can also set wait fields directly:
Waits honor cancellation, so stopping the drawer does not leave a sleeping process behind.

Failure handling

By default, a failing step fails the drawer. Step-level on_failure can be authored in drawer.json for retry or continue behavior:
A drawer can also declare on_error to point at a separate error-handler drawer, but this is reserved: the v1 executor parses it and does not yet run it. The field exists so a drawer can declare the intent ahead of the executor. When it ships, the handler will receive drawer, run_id, failed_step, error, and redacted inputs, and the failure will still remain visible in the original drawer run history.

Webhook-triggered drawers

Webhook triggers currently attach to drawers:
The request appears as ${inputs.webhook} inside the drawer:
Webhook is the only trigger kind drawers support today; registering any other kind errors. The trigger can require auth — append --auth basic|header|jwt (plus the matching --auth-user/--auth-pass, --auth-header-name/--auth-header-value, or --jwt-secret flags) to trigger webhook; the default is --auth none. Secret values accept $ENV{VAR}, resolved at match time, so drawer.json stays commit-safe. buttons webhook listen runs the foreground dispatcher behind a Cloudflare tunnel, so it requires cloudflared on your PATH. Run buttons webhook setup once for a stable hostname, or let listen spin up an ephemeral *.trycloudflare.com URL per run. Test a webhook drawer without standing up the listener by synthesizing the request payload at press time:

Inspect and debug

--summary previews a drawer mutation or press without running it. Use --json when an agent or script needs structured output. buttons drawer schema prints the embedded JSON Schema for drawer.json. See drawer.json for the full field reference.

Storage shape

A drawer folder looks like this:
The history file records drawer inputs, each step’s resolved args, stdout, stderr, parsed output, duration, exit code, and structured errors. That is what lets agents recover from workflow failures without re-running blind.