A Booking Is a Thing at a Time
The shape of the assumption
Every booking in Airflow used to be three facts: a check-in date, a check-out date, and a number of nights.
That is a perfect description of a stay and a completely useless description of almost everything else. A dinner table at 19:30 has no nights. A two-hour appointment on Thursday morning has no checkout. A Saturday departure carries twelve people rather than occupying a building. A pottery class has a start time, a capacity, and eight more of itself next month.
This mattered because of something a host said to us plainly: a single accounting package can sell many different booking types — a wedding venue has accommodation, events, facilities for hire, professional services, and so on. Not five businesses. One business, one set of books, one calendar, five kinds of thing being sold.
We had already written about adapting to different kinds of operation in One Platform, Every Service. What we discovered on inspection was that the adaptation had never actually reached the product.
What we found when we looked
An honest inventory, because the cleanup is most of the story:
The type was stored in two places — on the organisation and on the resource — and nothing reconciled them.
Three different vocabularies disagreed. One dropdown offered five types. Another offered nine. The website translations had fourteen. There was no constraint anywhere enforcing which were real.
The labels had never reached the portal at all. Thirteen pages, the menu and the footer were all wired to display type-specific wording — “Guests” versus “Attendees” versus “Clients” — and no part of the system ever supplied it. Every host, whatever they ran, had always seen the hard-coded defaults. The clever fallback for mixed accounts had never once fired in production.
And changing a resource’s type didn’t save. The dropdown posted to an endpoint whose permitted-fields list didn’t include it. The interface flipped, you felt pleased, you reloaded, and it was back. That had presumably been true for months, and nobody had reported it, because every account on the platform was a property.
The rebuild
The type belongs to the resource. Not the organisation. The organisation’s setting is now a default for new resources and a note about where the customer came from; an account’s character is derived from what it actually contains, and “mixed” is a legitimate answer.
One vocabulary, ten types, five families. Property, event, experience, tour, service, facility, equipment, vehicle, boat and charity. Every dropdown renders from that one list. Families group them where behaviour is shared. “Rental” stopped being something you could select and became the family that holds vehicles, boats, equipment and facilities.
The wording follows the type, computed server-side. An events resource says Attendees and Tickets. A tour says Travellers, Departure and Return. Equipment hire says Renters, Rentals, Pick-up and Return. A charity says Donors and Donations. A mixed account falls back to neutral wording rather than picking a winner. One navigation entry, labelled with the plural of your type when everything in view shares one, and type tabs only when there’s more than one.
And one pricing engine prices all of them. This is the part that actually matters. Rather than a pricing path per vertical, there is a single engine that prices a booking by its unit: per night for accommodation, per day or week for hire, per person, or per booking multiplied by tickets for events, services and charity. Booking classes, per-class capacity and a shared availability rule work across all of them, and quote-equals-charge has been proven live rather than assumed.
Then: what time is it?
Types got us most of the way and left one thing untouched. A booking still had no time of day. Which meant two sittings on one evening, or three appointments in a morning, were not merely unsupported — they were inexpressible.
That’s what shipped this week, in two parts.
Sessions. A session is a real thing with a date, a label, a start and end time, a capacity, an optional price of its own, and notes. A booking points at one. Capacity is counted within the session — so a restaurant can run 19:00 and 21:00 on the same date, each with its own seats, and the bookings don’t contend. Where a booking has a time but no session, it gets its own time-range protection. And the original date-range protection that stops two guests occupying a house on the same night was narrowed so it now guards exactly what it always guarded, and nothing else.
Any family can have sessions. Sittings, departures, appointment slots, class times — the same mechanism.
Repeats. Nobody is going to create “Tuesday 19:00” fifty-two times. So a pattern describes the repeat — weekdays, times, capacity, price, a validity window — and occurrences are generated out to the property’s own booking horizon.
We considered three ways to build that and the choice is worth recording. Creating every occurrence in advance means endless rows and a job to maintain them. Keeping them purely virtual means a booking has nothing to point at and capacity has nowhere to accumulate. So: materialise on first touch. One function returns real and virtual occurrences together with booked and remaining counts, feeding every surface from one place — the portal, the manual booking form, the host list, the guest planner. The moment a booking or a host override needs a particular occurrence to be real, it becomes real. Quotes never materialise anything.
Host overrides fall out of this neatly rather than needing their own concept. Closing one Tuesday is that occurrence made real and switched off. A special price on one date is that occurrence made real and edited.
What’s live now: the schema, the engine, the quote and booking functions, the portal’s Sessions card with its Repeats block, manual session booking, and the host booking list showing seats left per session.
What you can’t do yet, and why
This is where we have to be straight, because everything above could easily read as an invitation to go and list a restaurant.
Every non-property type is in demo mode: admin-only, and unpublishable.
A host can’t select one. A resource with one of those types cannot be published, cannot appear on the marketplace, and cannot take direct bookings. No guest can reach one. That’s enforced on the server, not hidden in the interface, and we tested it by trying — as an admin, who could create and edit but was refused publication, and as an ordinary account holder, who was refused at every write.
The reason is a rule we set ourselves: a type opens when a real booking has been walked end to end for its family. Quote, checkout, payment, confirmation email, invoice, guest portal, the lot. Not when the code looks finished.
Today, every account on the platform runs properties. Ten types exist, one is open, and the rest are being walked one family at a time. Emails and PDFs still use accommodation wording in places, and the first walked booking on a non-property type is the next piece of work.
Why tell you now
Because the interesting thing has already happened, and you can see it in what your own property does.
One pricing engine now prices every unit. Sessions and repeating sessions exist and run. The type lives where it belongs. The labels reach the screen. The model underneath your nightly booking stopped being shaped like a night — it’s shaped like a thing, at a time, with a capacity, and a night is one case of that.
When a restaurant, a tour operator or a clinic does open, it won’t be a separate product with its own invoice logic and its own pricing quirks. It’ll be the same engine, the same checkout, the same books.
We’d rather show you the machinery running and tell you which doors are still shut than announce five new verticals and let you discover the difference.
Related: Airflow Is Not a PMS on what this is instead, One Resource or One Hundred on scaling across a portfolio, and What’s Next for Airflow. Or look at how it works.