Seat selector

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.

Supporting organizations: 
Development

Project information

Releases