Yoyaku is a lightweight, concurrency-safe resource booking and availability API for Drupal. It gives any module a small set of entities and a service API to define bookable resources, publish availability slots, and take bookings without ever overbooking, even under concurrent requests.
It models everything from a single room booked by the hour to a ticketed venue with numbered seats, without changing the core: every axis of variation lives behind a named seam with a shipped default.
Why Yoyaku
Most booking solutions decide availability by counting rows, which races under load and double-books the last place. Yoyaku treats capacity as a first-class, concurrency-safe concern: every hold checks and reserves capacity atomically inside a database transaction with a row lock on the slot, so two simultaneous requests can never overbook. Holds carry a time-to-live and expire automatically, releasing capacity when a reservation is abandoned.
Performance
A booking opening is the one moment a booking site is judged on. A party is seated under the very lock that takes its seats, so nobody is offered a seat another booker is already taking, and a booker is refused only once the house is genuinely gone. What a hold holds does not grow with the hall: a stadium of 80,000 seats in eighty parts sells at the rate of one arena of 8,000, whether its bookers pick seats themselves or take a part of the house and are seated inside it. The harness that measures this ships with the module.
Core concepts
- Resource: a bookable item (room, equipment, production) with a default capacity and a booking window.
- Slot: a concrete time window, with an optional slot-wide capacity cap.
- Tariff: what a resource offers a booking at, with its own quota and price.
- Slot tariff: one session's departure from a tariff, for a different quota, price or allotment on that date alone.
- Tariff class: the audience a tariff is offered under (Adult, Child, Subscriber).
- Allotment: a named slice of a session's capacity that given tariffs draw from. Twenty places held for the schools, or the front row held by name.
- Booking: capacity consumption, following a held-to-confirmed lifecycle.
- Tenant: the realm rows belong to; a multi-tenant install keeps them isolated.
Features
- Bookable rules: constraint plugins enforced at hold time, plus constraint policies attached and configured per resource, resource type or booking channel.
- Tariffs: tariff classes, per-session overrides, and presets that let a grid of tariffs and allocations be typed once and stamped onto every production that runs it.
- Allotments: hold part of a session's capacity under a name, released on a deadline or held until somebody takes it.
- Numbered and pooled places: a venue map, places held by name for an allotment, seating a party together, and booking units for what a place means in a given hall.
- Booking channels: which surface may offer what, resolved from the domain being served.
- Booking fees: what a front charges on top of what the booking costs, computed by plugins a channel attaches and sums, so the same place can carry a fee online and none at the counter.
- Built for a rush: the calendar and the seat map answer thousands of simultaneous visitors from one calculation, while every hold still re-checks capacity under the slot lock.
- Cart and checkout: one basket across surfaces, and a booker who abandons checkout is returned to where they left off.
- Payments: payment-gated confirmation and a guarantee lifecycle.
- Cancellation: self-service, with deadline and refund policies.
- Tickets and check-in: PDF tickets with QR codes, and scanning at the door.
- Recurrence: generate a season's slots from a rule.
- Lifecycle workflow: via Orchestra integration.
- Multi-tenancy and delegated administration.
- Built to extend: lifecycle events and hooks throughout.
- Translated: a French interface translation is included.
What is included
The core module is headless and dependency-free (nothing beyond Drupal core). Presentation, ticketing, workflow and payment live in optional submodules you enable as needed:
- Yoyaku UI: admin screens for resources, slots, categories and bookings, with generation and bulk tools.
- Yoyaku Calendar: a public calendar element to pick a slot and place a booking, as a block or field formatter.
- Yoyaku Placement: venues, sections, place grades, numbered or pooled places, and the graphical place map.
- Yoyaku Cart: the cross-surface basket and checkout.
- Yoyaku Node: bind a bookable resource to a node and show its calendar on the node.
- Yoyaku Webform: use a Webform as the booking form step.
- Yoyaku Order: group the bookings of one submission into a persistent order, and attach constraint policies.
- Yoyaku Payment: guarantees, refunds and payment-gated confirmation, over a Kessai gateway.
- Yoyaku Booking Fee: what a front charges on top of what the booking costs, configured per booking channel. Ships under Yoyaku Payment; a site that never charges one never enables it.
- Yoyaku Ticket: PDF tickets with a QR code, and the check-in screen that scans them.
- Yoyaku Orchestra: drive the full booking lifecycle as an Orchestra workflow.
- Yoyaku Manager: scoped administration by resource, resource type or tenant.
- Yoyaku Domain: resolve the booking tenant from the domain being served.
- Yoyaku Views: expose bookings, resources, slots, orders and payments to Views.
- Yoyaku API: a transport-agnostic client contract, with an in-process implementation.
Requirements
- Drupal 11.3 or newer
- PHP 8.3 or newer
- MySQL or MariaDB, not SQLite. Not overbooking is the whole promise, and what keeps it is a row lock: a hold reads what a session has already taken with SELECT ... FOR UPDATE, so a second request waits for the first to commit instead of reading a stale answer beside it. Drupal's SQLite driver drops that clause silently, which leaves the locking read an ordinary read and the guarantee unenforced: the site looks like it works and hands two bookers the same seat under a rush. PostgreSQL implements the clause and should work, though the project has not been run against it.
- The booking, calendar, placement, cart and order features stand on their own. Payments require Kessai, the lifecycle workflow requires Orchestra, and domain-based tenancy requires Domain.
- PDF tickets additionally need two Composer libraries:
composer require dompdf/dompdf endroid/qr-code.
Documentation
Full documentation, covering the concepts, a no-code getting-started guide, the calendar, placement, tickets, check-in, constraints, allotments, booking fees, multi-tenancy and the API reference, is published at project.pages.drupalcode.org/yoyaku.
Project status
Yoyaku is under active development and has not yet reached a stable release. Interfaces may still change before a 1.0 release, and no database update hooks are provided between pre-releases, so moving between pre-releases means reinstalling rather than upgrading in place. Issues and merge requests are welcome.
Project information
- Ecosystem: Orchestra Workflow Engine, Kessai Payment Engine
- Created by mably on , updated
Stable releases for this project are covered by the security advisory policy.
There are currently no supported stable releases.
Releases
Development version: 1.x-dev updated 21 Oct 2026 at 12:48 UTC



