
Engineering
Bulk-transition hundreds of records at once
FastYoke Engineering · 6 min read · Jul 7, 2026
- Tutorial
- Bulk ops
- Workflow
What you'll build
You have a few hundred records sitting in the same workflow state — say
two hundred order records stuck in awaiting_dispatch after a carrier
outage cleared — and you need every one of them to fire the same
transition. Doing that one row at a time is a non-starter, and firing raw
UPDATEs against the state column would skip the workflow entirely: no
guard check, no audit trail, no live update for anyone watching.
Bulk operations solve exactly this. You select the records, choose one FSM transition, preview what would happen row-by-row, and then commit. Every record that succeeds runs through the same transition engine a single-record change runs through — its guard is evaluated, its state is updated, and a row is appended to the append-only event log. When you're done, the history reads as if you'd fired two hundred transitions by hand, because in every way that matters, you did.
Before you start
You need three things:
- An app with an entity that has an FSM. Bulk transitions operate on the entity's underlying workflow — the same states and edges you author in the workflow builder. If your entity has no state machine, there's nothing to transition.
- The transition you want to fire, by event name. A bulk transition fires one event across the whole set. Know the event name (the same one on the edge in your workflow) before you start.
- The admin role. Bulk operations are admin-only — every endpoint rejects a non-admin caller. There's no per-permission gate beyond that, and, worth stating plainly, no tier caps: every tier that has admin users can run bulk operations.
Steps
1. Select the records
Open the entity's list view and pick the rows you want to move. There are two ways to describe a selection:
- By id — checkbox the specific rows you want. This is the natural fit when you're eyeballing a filtered list and hand-picking.
- By filter — describe the set with a filter expression instead of enumerating ids. The platform snapshots how many records match at preview time and re-counts at commit, so anything that drifted out of the filter in between is caught rather than silently transitioned.
Either way, the selection plus your chosen operation — here, a transition carrying one event name — is what you're about to preview.
2. Preview the change
Bulk operations are always two-step. Nothing touches your data until you commit; the first step computes a preview and hands you a row-by-row verdict. The preview reports how many records resolved in total, how many would succeed, how many would skip, and how many would error — plus a sample of the affected rows and the full list of per-row problems.
This matters most for transitions, because the guard runs per record.
A transition edge can carry a guard — a sandboxed
JSONLogic predicate that decides
whether this particular record is allowed to move. Bulk doesn't bypass
it. Each record is evaluated on its own, so a set of two hundred can come
back as a hundred and ninety clean and ten flagged guard_failed,
meaning the guard returned false for those ten. A record sitting in a
state that has no outbound edge for your event surfaces as
invalid_fsm_from_state. The preview is your one chance to see all of
this before anything changes — read it.
3. Commit — or cancel
If the preview looks right, commit the session. If it doesn't, cancel it;
cancel is only reachable from the previewed state, and it moves the
session cleanly to cancelled without touching a single record.
On commit, each row is applied independently. A transition that succeeds
runs the real engine: the guard evaluates, current_state updates, a row
lands in the append-only event log recording the from_state and
to_state, and a WebSocket event fires so any live client watching that
record reflects the move immediately. It is the exact same path a
one-off transition takes through the API — bulk simply drives it once per
selected row.
Per-row errors don't abort the run. The session records succeeded,
skipped, and errored counts and keeps going; the terminal errored
session status is reserved for an infrastructure failure that stopped the
commit outright, not for a guard that legitimately said no to ten rows.
When it finishes, you can pull an errors.csv for the full per-row
report — record id, current state, outcome code, and message — ready to
drop into a spreadsheet so you can fix and re-run just the stragglers.
For large sets the mechanics shift slightly: commits under a thousand rows run synchronously and return when done, while a thousand rows or more enqueue on the background worker and return immediately with a session id you poll until it reaches a terminal state. Either way the per-row behavior is identical.
Take it further
The audit trail is the part worth dwelling on. Because bulk transitions reuse the single-record engine, every successful row gets the same natural event-log entry a manual transition would — and bulk adds one more: a companion audit row that threads the operating admin and the bulk session id alongside it. The event log is append-only by construction, so that record can't be edited or pruned later. Six months from now, "who moved these two hundred orders, when, and in which batch?" is a query, not a guess.
That same append-only log is what powers the rest of the workflow story: webhooks that fire on a state change see every one of these transitions, a Kanban board over your states redraws itself as the WebSocket events land, and the job event log reads the same whether a transition came from one operator clicking a button or a bulk session moving hundreds at once.
One deliberate boundary: bulk operations cover edit, delete, and transition — there's no bulk insert, no cross-entity batch, and no rollback of a committed session. If you need to undo a transition, you fire the reverse edge (as another transition, bulk or single), which leaves its own honest row in the log rather than erasing history.
Related
- Bulk operations — the full feature: the three operations, both selection modes, and the preview-commit lifecycle.
- Bulk operations API reference — the REST endpoints, the five session statuses, and the eight per-row outcome codes.
- Workflows and the workflow builder — where the FSM, its edges, and its guards are authored.
- Jobs and the event log — the runtime side of a workflow and the append-only audit trail every transition writes to.