Appearance
Bookings
Online appointments: services, staff and their hours, free slots on the site, bookings the staff confirm from an email.
kitloom/bookings · admins: kitloom/bookings-filament, kitloom/bookings-nova
Online appointments for a salon, a barber, a studio, a clinic: clients pick a service, a staff member (or whoever is free) and a free time on the site; the staff member gets an email with Confirm and Decline at once; the client gets the answer with a calendar file. A fresh module of its own tables — not a copy of a WordPress booking plugin.
The admin is kitloom/bookings-filament or kitloom/bookings-nova. A theme draws the booking form (kitloom/atelier-theme has one).
Setting it up
Settings → Opening hours. A row per stretch: Tuesday 09:00–13:00 and Tuesday 14:00–19:00 is a lunch break. A day without rows is closed. The times are the site's (Settings → General → Timezone).
Services. The name, how long it takes, the pause the staff member needs after it (cleaning, a coffee), the price, a picture. A service switched off leaves the site and stays on its past bookings.
Staff. The profile the site shows; the services each does — at their own price or pace, if they differ; their own Hours (none — they work the opening hours); Time off. The email is where their new bookings go. Linked to a site user, they also see their day on the account page and decide there.
Settings → Booking online. How often times are offered (every 15 minutes…), how soon (at least 2 hours ahead) and how far ahead (30 days) clients may book, until when they may cancel online, whether bookings are confirmed at once.
Settings → Emails. "Also notify" — the front desk, which hears of every booking; the reminder before the visit; the address printed in the emails.
Services → Remind to book again after (weeks) and Settings → Booking page: weeks after a visit marked done, the client gets a "time to book again" email whose button opens the booking page with the same service and staff member.
A whole day closed — a public holiday — is time off of nobody in particular (CatalogWriter::closeSalon(), or an agent).
Clients
Every booking belongs to a client's card (Bookings → Clients). A booking from the site finds its card by who made it: the signed-in user's card, else the same email, else the same phone (its digits, whatever the spaces), else a new one. A booking made in the admin picks the client by name or phone, or makes a new one; a walk-in known by phone needs no email. Bookings made before cards existed got theirs when the module was updated.
The card shows the visits, no-shows, cancellations and the money spent, the last visits and the next booking, the client's own note (they write it on the account page: "Sensitive scalp, no ammonia") and the salon's notes — dated, signed, only ever added, never seen by the client. The staff member sees the same in a line in the new-booking email, on the page its buttons open and in their schedule on the account page, where they add notes about their clients too.
Time to book again: a service with weeks set sends one email per visit marked done, that many weeks after it — not when the client has a booking ahead or has been since for the same service, not for visits more than four weeks past their time (turning this on does not write about last year), and not to a client who switched these emails off (from the email's link, the account page, or the card).
Export data gives a client everything kept about them as a JSON file; Forget client removes the card and the notes, and leaves their bookings for the books — when, what, by whom, for how much — without a name, email, phone or note.
How a booking goes
| Step | What happens |
|---|---|
| The client books | The time is checked again under a lock: two clients pressing "Book" on the same time — the second is told it was just taken. The booking is pending (or confirmed, with "Confirm bookings at once"). |
| The staff member hears | An email to the staff member and the front desk: the client, the time, the note, and the buttons Confirm, Decline and "Decline and block this client". |
| They decide | A button opens a page with one more button — mail apps that open links to check them decide nothing. The links need no sign-in and work until the appointment. |
| The client hears | Confirmed: an email with the calendar file (.ics — Google, Apple, Outlook open it) and a link to manage the booking. Declined: an email with the staff member's word. |
| Before the visit | A reminder, with the calendar file again. |
| After it | Done or No-show, in the admin or on the account page. |
A client cancels online until the salon's limit (12 hours before by default), from the link in their emails or the account page; after it the page asks them to call. The salon cancels or moves any booking any time.
Languages. A booking made on a page in French keeps its language: the client's emails are French, and so are the pages their links open — the booking, its cancellation, the calendar file, the reminders — with the site's French texts and links to its French pages (/fr/), also from emails sent before. The staff's emails and the pages their buttons open stay in the site's language.
Blocked clients cannot book online: "Decline and block" adds the email to Settings → Blocked clients, which the salon edits too. They are told to call, nothing more.
One client, three bookings ahead. A client — one email or one account — may hold up to three upcoming bookings made on the site; the fourth is asked to call. Cancelled, declined and past bookings don't count, and the salon books for them freely. The number is limits.open_per_client in the configuration (0 — no limit).
The admin books for a client on the phone or at the door any time the staff member is free — in hours or out of them; the client gets the emails.
For the theme
| What | Where |
|---|---|
| Services, staff, rules, addresses | the page part booking (Site\BookingData documents the keys) — on the pages whose template lists it |
| Free times | GET /wp-json/bookings/v1/slots?service=3&date=2026-11-03 (&staff=5); a day strip: ?service=3&from=2026-11-01&days=14 |
| Booking | POST /wp-json/bookings/v1/bookings — service, staff (empty: anyone), start (a slot's start), name, email, phone, client_note, the form protection's fields (booking.protection); 201 with the booking and the client's manageUrl, calendarUrl; 409 booking_slot_taken |
| Account page | sections appointments (the client's bookings, cancel; their own note and reminders — POST /wp-json/bookings/v1/me/note) and schedule (a staff member's day with each client's history, the decisions — POST /wp-json/bookings/v1/bookings/{code}/{action}, a note — POST /wp-json/bookings/v1/clients/{id}/notes) |
Where things are kept
| What | Where |
|---|---|
| Services | table booking_services (the site's table prefix in front) |
| Staff, what each does, their week, time off | tables booking_staff, booking_staff_services, booking_schedules, booking_time_off |
| Bookings | table bookings — moments in UTC; the price, the length and the pause as booked; client_id |
| Clients and their notes | tables booking_clients, booking_client_notes |
| Settings | options bookings_* (BookingsOptions); the opening hours one option, weekday => stretches |
Everything is written by BookingWriter (bookings), CatalogWriter (services, staff) and Clients (cards, notes, export, forgetting) with the acting user — the admins, the routes, the agents alike. Who may: manage_bookings (administrators and editors) — everything; a staff member linked to a user — their own bookings; a client — cancelling their own. Events: BookingRequested, BookingStatusChanged (from, to, the actor).
Emails and the scheduler
The emails go through the application's mailer and queue. The reminders — before a visit and "time to book again" — are php artisan bookings:remind, which the scheduler runs every quarter of an hour — the server needs php artisan schedule:run every minute. One server of several sends them, by a lock in the cache, so a site on several servers needs a shared cache store (Redis, the database) — with each server's own cache the lock is local and the task runs on every one.
AI agents
With kitloom/ai: bookings_catalog, bookings_slots, bookings_list, bookings_create, bookings_decide, bookings_clients, bookings_client, bookings_client_note, and the rules in describe_site. An agent with manage_bookings also keeps the catalog: bookings_service_save and bookings_staff_save make and change services and staff — with the services each does, their weekly hours and time off, in the site's time — bookings_staff_get reads one staff member as the save writes them, and bookings_close closes the whole salon for a holiday. Nothing is deleted: active: false takes a service or a person off the site. Which user account a staff member signs in with stays a person's choice in the admin — that user sees the clients. Off with kitloom-bookings.ai.register.
Personal data
With kitloom/privacy installed, WordPress's Tools → Export Personal Data and Erase Personal Data reach the clients. An address finds the card with it and the card of the site user who signs in with it. The export holds the card, the salon's notes about the client and their bookings — and the bookings made with the address that are on no card of theirs. Erasing is Forget client: the cards and the notes go, and every booking made with the address stays for the books without a name, email, phone or note — the report says so. A booking on another client's card keeps that card. kitloom-bookings.privacy.register false — the module does not take part.
What the application overrides
- The models:
ModelRegistry::use(Booking::KEY, App\Models\Booking::class)(alsoService::KEY,Staff::KEY,Client::KEY); the admins read the classes from the registry. config/kitloom-bookings.php(php artisan vendor:publish --tag=kitloom-bookings-config): the routes, their rate limit and the emails' pages' address (/booking/{code}), the bookings one client may hold ahead, the emails, the account sections, the agents' tools, personal data requests, the admin sections.
Tests
From the application that installs the package: php vendor/bin/phpunit --testsuite Bookings.