Version 1.3.0
July 26, 2026
{% include 'booked/frontend/wizard' %}now renders the framework-free vanilla wizard by default — no template changes needed. The deprecated Alpine wizard is available for one release window via thelegacyWizardsetting (or{% include … with { legacyWizard: true } %}), logged as deprecated, and removed in 2.0.
Added
- Framework-free booking wizard — a zero-runtime-dependency rewrite of the booking flow: a headless state-machine core (
BookedWizard.create()) plus a vanilla renderer, replacing Alpine.js. Runs under a strict CSP (nounsafe-eval, no inline executable script — config is read from a JSON block), ~14.5 KB gzipped (core + renderer). See VANILLA_WIZARD.md. - Accessible calendar replacing Flatpickr: WAI-ARIA date grid with roving tabindex, full keyboard navigation (arrows / Home-End / PageUp-Down / Enter-Space), month crossing, and a two-click range mode for multi-day services. Month, weekday, and full-date names are localized from the site language via
Intl. - Booking-management mode (
?manage=<token>) — a self-service view to cancel, reduce, or increase the quantity of an existing booking, driven by the reservation's confirmation token. - Multi-seat quantity pickers for slots, multi-day ranges, and events (bounded by remaining capacity), with required-extras enforcement.
- Captcha widget support — Turnstile, hCaptcha, and reCAPTCHA v3, with the token minted/refreshed before submit.
- Versioned headless API at
/booked/api/v1/…— the wizard's REST surface as documented public API, aliased onto the existing controllers so the old paths keep working. - Soft-lock hold timer — the wizard now shows and manages the reservation countdown, auto-extends once on submit, and cleanly enters an expired state (new
slot/extend-lockaction +SoftLockService::extendLock(), capped bysoftLockMaxLifetimeMinutes). - Bring-your-own-frontend — the core ships as a headless ESM/UMD build (
booked-wizard-core) drivable with no DOM, for custom/React/Vue frontends.
Parity (vanilla wizard)
- Full feature parity with the Alpine wizard: service, extras, location, employee, single-slot / fixed-day / flexible-day date selection, multi-seat quantity, customer info, review summary, and Commerce redirect; event-date flow (incl. paid events and per-event quantity); booking management; waitlist branch; honeypot anti-spam.
- Styling matches the previous wizard — the same editorial black-and-white design (2px borders, square corners, uppercase controls), driven by
--booked-*CSS custom properties for theming. - The review summary and confirmation screen show only the rows that apply (no empty labels), and prices display with the Commerce currency symbol.
Accessibility
role="region"landmark, live-region step announcements, focus moved to each step heading,role="alert"errors,aria-pressed/aria-selectedselection state,aria-requiredon required fields, and full-date accessible names on calendar days.
Internationalization
- All runtime strings the core emits (announcements, countdown, validation, errors) and the calendar's navigation labels are translatable and shipped in en, de, fr, es, it, ja, nl, and pt.
Version 1.2.1
June 17, 2026
Added
- MCP authorization settings (Settings → Security): Allow MCP write operations (
mcpWriteEnabled) and Allow MCP refunds (mcpAllowRefunds), both off by default. A migration adds the columns to existing installs.
Security
- MCP write tools are now default-deny: every create/update/cancel/delete/refund tool is gated behind
mcpWriteEnabled(and refunds additionally behindmcpAllowRefunds), rather than relying on the craft-mcp server config alone (this supersedes the 1.2.0 "authorization is delegated to the craft-mcp server" note). In a web context an authenticated user must also holdbooked-manageBookings. booked_refund_reservationis now idempotent — it nets out prior refunds and never refunds more than the outstanding (paid minus already-refunded) amount.- Customer PII (email/phone) is now redacted by default in all MCP responses — including create/update/booking responses, not just bulk lists — so a forgotten flag fails safe.
- MCP list tools clamp their page size to a hard ceiling (200) to prevent unbounded result sets (memory exhaustion / bulk data export).
Fixed
booked_delete_event_dateon an event that still has reservations now returns the actionable "retire it withenabled=falseinstead" message (via a typed exception) rather than a generic internal-error response.
Version 1.2.0
June 17, 2026
Added
- MCP integration: Booked now registers ~50 tools with the optional craft-mcp plugin, exposing near-complete headless admin to AI assistants — services, employees, locations, schedules, blackout dates, service extras, availability, reservations, event dates, waitlist and reporting. Covers reads, create/update (soft-disable via
enabledrather than hard delete), reschedule, quantity changes, refunds and analytics. The dependency is soft (class_exists-guarded) — Booked runs unchanged when craft-mcp is absent. See MCP.md. - MCP safety model: customer email/phone are redacted on every reservation/waitlist read (not just bulk lists); booking capability tokens and virtual-meeting URLs are never exposed; cancellations always run the refund/capacity-release flow (status cannot be force-set to
cancelledvia update); retired (disabled) services/events remain listable and re-enablable; inputs are validated (quantity ≥ 1, employeeserviceIdsmust exist); and notification/refund side effects are rate-limited with a fixed-window, mutex-guarded limiter (separate budgets, charged only on success) since Booked's IP-based limiter does not apply over MCP. Authorization is delegated to the craft-mcp server (IP allowlist / dangerous-tool gating). All 50 tools verified end-to-end against the live MCP server.
Version 1.1.1
April 15, 2026
Added
- New
slot/get-range-capacitycontroller action andBookedAvailability.getRangeCapacity()JS helper for querying the remaining capacity of a multi-day date range (tightest day wins). ScheduleResolverService::getCapacityForDay()resolves day-based capacity fromSchedule.workingHours[day].capacity, honoring service → employee → aggregated-employee precedence.- Day-service wizard step now shows a quantity picker (with +/− controls and live remaining-capacity hint) when a multi-day range has capacity greater than one.
- Inline booking error banner on the date/time step so backend error messages surface to the user instead of being swallowed.
Changed
- Multi-day availability now enforces capacity per day using the resolved schedule capacity instead of
Service.capacity, correctly accounting for overlapping reservations across each day of the range. SlotControllerrate limits raised to 120 req/min acrossget-slots,get-dates,get-valid-end-dates,get-event-dates, andget-availability-calendarto reduce false 429s for legitimate wizard traffic.BookedAvailability.getDates()andcreateBooking()now parse error response bodies so the wizard can display the server message and status code.- Wizard caches fetched available-date results per month/service/employee/location/quantity/extras key and de-dupes in-flight requests.
- Flexible-day price calculation guards against missing
selectedService.price.
Fixed
- GraphQL mutation registration for
QuantityMutations,ReservationMutations, andWaitlistMutations(minor correction). - Day-service “Next” button enable/disable logic now accounts for day-range capacity picker state.
Version 1.0.2
April 2, 2026
Fixed
- Bundled Alpine.js with the plugin so the built-in booking wizard works out of the box without requiring the site theme to include Alpine separately (#3)
- Alpine.js is loaded at
POS_ENDto ensure proper initialization order with wizard components - Added detection to skip loading Alpine.js if the site already includes it