Booking Features
Covers duration types, multi-resource bookings, and capacity/inventory management.
Covers duration types, multi-resource bookings, and capacity/inventory management.
Duration Types
Set on each service via the durationType field. Controls how endTime is calculated.
Fixed (default)
endTime = startTime + service.duration
The standard appointment mode. The service duration is fixed and always applied. Used for haircuts, consultations, classes with defined runtimes.
{ duration: 60, durationType: 'fixed' }
// A 60-minute appointment — endTime is always startTime + 60 minFlexible
endTime is provided by the caller in the booking request. The service duration field acts as the minimum; if the provided endTime results in less than duration minutes the booking is rejected.
Used for open-ended services where the customer specifies how long they need — workspace rentals, recording studios, vehicle bays.
{ duration: 30, durationType: 'flexible' }
// Minimum 30 minutes, but the caller can book 90 minutes by providing endTimeWhen creating a flexible booking, pass both startTime and endTime:
await payload.create({
collection: 'reservations',
data: {
service: flexibleServiceId,
resource: resourceId,
customer: customerId,
startTime: '2025-06-15T10:00:00.000Z',
endTime: '2025-06-15T12:30:00.000Z', // 2.5 hours
},
})Full-Day
endTime = end of the calendar day (23:59:59) relative to startTime.
Used for day-rate resources: hotel rooms, venue hire, equipment daily rental.
{ duration: 480, durationType: 'full-day' }
// Always occupies the entire day, regardless of start timeMulti-Resource Bookings
A single reservation can include multiple resources simultaneously using the items array. This is used for bookings that require a combination of resources — a couple's massage (two therapists), a wedding (venue + catering team), a film shoot (studio + equipment set).
The top-level service, resource, and startTime fields represent the primary booking. Additional resources go in the items array:
await payload.create({
collection: 'reservations',
data: {
service: primaryServiceId,
resource: primaryResourceId,
customer: customerId,
startTime: '2025-06-15T14:00:00.000Z',
items: [
{
resource: secondResourceId,
service: secondServiceId,
startTime: '2025-06-15T14:00:00.000Z',
endTime: '2025-06-15T15:00:00.000Z',
guestCount: 2,
},
{
resource: thirdResourceId,
// service is optional — inherit primary if omitted
},
],
},
})Each item in the items array has its own resource, optional service, optional startTime/endTime (for staggered scheduling), and optional guestCount.
Inheritance rules: Items missing startTime, endTime, service, or guestCount inherit the parent reservation's values.
Validation:
- Every item must have a
resourceandstartTime(either its own or inherited from the parent). Items missing required fields throw aValidationErroridentifying which item is incomplete (e.g.,items.1.resource). - Duplicate
(resource, startTime)pairs within the same booking are rejected. - Conflict errors include the item index (e.g.,
items.2.startTime) so you know which item failed.
Conflict detection runs independently for each resource in the items array. Each item's own service determines its buffer times (bufferTimeBefore/bufferTimeAfter), so different items can have different buffer windows.
Capacity and Inventory
By default, each resource allows only one concurrent booking. Set quantity > 1 to enable inventory mode.
quantity
The number of concurrent bookings the resource can accept for overlapping time windows.
await payload.create({
collection: 'resources',
data: {
name: 'Standard Room',
services: [hotelNightId],
quantity: 20, // 20 identical rooms
capacityMode: 'per-reservation',
},
})With quantity: 20, up to 20 reservations can overlap. The 21st booking for the same time window is rejected.
capacityMode
Controls how the quantity limit is counted. Only relevant when quantity > 1.
per-reservation (default): Each booking occupies one unit, regardless of how many guests it contains. Use this for hotel rooms, parking spaces, equipment units, or any resource where each booking takes one slot.
quantity: 5 allows 5 simultaneous bookings
Booking with guestCount: 3 still occupies 1 slotper-guest: Each booking occupies guestCount units. Use this for group venues, yoga classes, boat tours, or any resource with a total people capacity.
await payload.create({
collection: 'resources',
data: {
name: 'Yoga Studio',
services: [yogaClassId],
quantity: 20, // 20 total spots
capacityMode: 'per-guest',
},
})
// Booking with guestCount: 3 occupies 3 of the 20 spots
// When 20 total guests are booked, the class is fullStatus Machine
The status machine controls the full lifecycle of a reservation — which statuses exist, which transitions are allowed, which statuses block a time slot, and which are terminal.
Hooks API
Hook callbacks that fire at key points in the booking lifecycle — beforeBookingCreate, afterBookingConfirm, afterStatusChange, and more.