Status Machine
The status machine controls the full lifecycle of a reservation — which statuses exist, which transitions are allowed, which statuses block a time slot, and which are terminal.
The status machine controls the full lifecycle of a reservation — which statuses exist, which transitions are allowed, which statuses block a time slot, and which are terminal.
Default Status Flow
| Status | Meaning | Blocks slot | Terminal |
|---|---|---|---|
pending | Created, awaiting confirmation | Yes | No |
confirmed | Confirmed and time slot committed | Yes | No |
completed | Service was delivered | No | Yes |
cancelled | Cancelled before the appointment | No | Yes |
no-show | Customer did not show up | No | Yes |
Terminal statuses cannot transition to anything. Once a reservation is terminal, it is permanently closed.
Blocking statuses control which statuses count as occupying the time slot for conflict detection. By default both pending and confirmed block the slot.
Custom Status Machine
Override any or all properties via the statusMachine option. Unset keys fall back to defaults.
- The
statusesarray drives the select field options in the admin UI - The
transitionsmap controls which updatesvalidateStatusTransitionallows - The
blockingStatusesarray determines which statuses occupy the slot in conflict detection - The resolved status machine is stored in
config.admin.custom.reservationStatusMachinefor admin component access
Config validation: The status machine is validated at plugin initialization. Invalid configs — such as a defaultStatus not in statuses, blockingStatuses or terminalStatuses referencing unknown statuses, or transition keys/targets pointing to non-existent statuses — throw an error at startup rather than causing silent runtime failures.
Business Logic Hooks
Four beforeChange hooks run on the Reservations collection on every create and update:
checkIdempotency— Rejects creates whereidempotencyKeyhas already been usedcalculateEndTime— ComputesendTimefromstartTime + service.duration(respectsdurationType)validateConflicts— Checks for overlapping reservations on the same resource using blocking statuses and buffer timesvalidateStatusTransition— Enforces allowed transitions defined in the status machine; on create, enforces that new bookings start indefaultStatus(admin users can also use statuses reachable fromdefaultStatus; usecontext.allowConfirmedOnCreatefor programmatic bypass)validateCancellation— When transitioning tocancelled, verifies the appointment is at leastcancellationNoticePeriodhours away
One afterChange hook also runs:
onStatusChange— Detects status changes; firesafterStatusChange,afterBookingConfirm, andafterBookingCancelplugin hooks
Escape Hatch
All hooks — both beforeChange and afterChange (including onStatusChange) — check context.skipReservationHooks and exit immediately when truthy. Use this for data migrations, seeding, and programmatic administrative operations where you want to handle side-effects (emails, payments) manually.
This is especially important for programmatic bulk updates. If you update a reservation's status with skipReservationHooks: true, the afterBookingCancel / afterBookingConfirm / afterStatusChange callbacks are not fired — preventing double-sends when you handle the notification yourself: