payload-reservepayload-reserve

Collections

Schemas for all five collections created by the plugin.

Schemas for all five collections created by the plugin.

Services

Slug: services

Defines what can be booked (e.g., "Haircut", "Consultation", "Massage").

FieldTypeRequiredDescription
nameTextYesService name (max 200 chars)
imageUploadNoService image
descriptionTextareaNoService description
durationNumberYesDuration in minutes (min: 1)
durationTypeSelectYes'fixed', 'flexible', or 'full-day' (default: 'fixed')
priceNumberNoPrice (min: 0, step: 0.01)
bufferTimeBeforeNumberNoBuffer minutes before the slot (default: 0)
bufferTimeAfterNumberNoBuffer minutes after the slot (default: 0)
requiredResourcesRelationshipNoExtra resource pools (hasMany) auto-expanded into the reservation items[] at booking time
allowGuestBookingSelectNo'inherit', 'enabled', or 'disabled' (default: 'inherit'). Controls whether anonymous guest bookings are permitted for this service
ownerRelationshipYes*Only present when resourceOwnerMode.ownedServices is enabled. Owner of this service (sidebar). Defaults to the requesting user on create
activeCheckboxNoWhether service is bookable (default: true)
await payload.create({
  collection: 'services',
  data: {
    name: 'Haircut',
    duration: 30,
    durationType: 'fixed',
    price: 35.00,
    bufferTimeBefore: 5,
    bufferTimeAfter: 10,
    active: true,
  },
})

See Booking Features → Duration Types for how durationType affects end time calculation.


Resources

Slug: resources

Who or what performs the service (a stylist, a room, a machine, a yoga instructor).

FieldTypeRequiredDescription
nameTextYesResource name (max 200 chars)
imageUploadNoResource photo
descriptionTextareaNoResource description
servicesRelationshipNoServices this resource can perform (hasMany). Optional so freshly provisioned staff resources can exist before services are assigned
activeCheckboxNoWhether resource accepts bookings (default: true)
quantityNumberYesHow many concurrent bookings allowed (default: 1)
capacityModeSelectNo'per-reservation' or 'per-guest' — shown only when quantity > 1
timezoneTextNoIANA timezone for display purposes
resourceTypeSelectNoDescriptive resource category. Options come from the resourceTypes config (default ['staff','equipment','room']); defaults to the first entry ('staff'). Drives no logic
ownerRelationshipYes*Only present when resourceOwnerMode is enabled. Owner of this resource (sidebar). Defaults to the requesting user on create. Relates to resourceOwnerMode.ownerCollection ?? staffProvisioning.userCollection ?? customers
await payload.create({
  collection: 'resources',
  data: {
    name: 'Room 101',
    services: [conferenceServiceId],
    quantity: 1,
    active: true,
  },
})

See Booking Features → Capacity and Inventory for how quantity and capacityMode work together.


Schedules

Slug: schedules

Defines when a resource is available. Supports recurring (weekly pattern) and manual (specific dates) types, plus exception dates.

FieldTypeDescription
nameTextSchedule name
resourceRelationshipWhich resource this schedule belongs to
scheduleTypeSelect'recurring' or 'manual' (default: 'recurring')
recurringSlotsArrayWeekly slots with day, startTime, endTime
manualSlotsArraySpecific date slots with date, startTime, endTime
exceptionsArrayDates the resource is unavailable. Each: date (required), endDate (optional — makes a full inclusive date range), type (select from leaveTypes config, default ['vacation','sick','personal','closure','other']), reason (text)
activeCheckboxWhether this schedule is in effect (default: true)

Times use HH:mm format (24-hour, e.g., 09:00, 17:30). The format is validated — values like 9:00 (missing leading zero) or 09:00:00 (with seconds) are rejected. Within each slot, endTime must be after startTime. Any date inside an exception (including a dateendDate range, where endDate must be on or after date) blocks out that entire day.

await payload.create({
  collection: 'schedules',
  data: {
    name: 'Alice - Standard Week',
    resource: aliceId,
    scheduleType: 'recurring',
    recurringSlots: [
      { day: 'mon', startTime: '09:00', endTime: '17:00' },
      { day: 'tue', startTime: '09:00', endTime: '17:00' },
      { day: 'wed', startTime: '09:00', endTime: '17:00' },
      { day: 'thu', startTime: '09:00', endTime: '17:00' },
      { day: 'fri', startTime: '09:00', endTime: '15:00' },
    ],
    exceptions: [
      { date: '2025-12-25', reason: 'Christmas', type: 'closure' },
      // A multi-day range (inclusive): the resource is off all week
      { date: '2025-07-01', endDate: '2025-07-07', reason: 'Vacation', type: 'vacation' },
    ],
    active: true,
  },
})

Customers

Slug: customers (or your userCollection slug)

Either a standalone auth collection (default) or fields injected into your existing auth collection when userCollection is set.

Standalone Mode (default)

A dedicated auth collection with auth: true for customer JWT login. Has access.admin: () => false to block customers from the admin panel.

User Collection Mode (userCollection set)

The plugin injects name, phone, notes, and a bookings join field into your existing auth collection. No new collection is created. The resolved slugs.customers points at the user collection so all downstream code uses the correct slug.

Field deduplication prevents double-injection — the plugin checks each field's name before injecting, so if your collection already defines name (or any of the others) the existing field config wins.

Fields

FieldTypeDescription
emailEmailCustomer email (from Payload auth)
firstNameTextFirst name (standalone mode only)
lastNameTextLast name (standalone mode only)
nameTextCustomer name (max 200 chars, required). Injected into the user collection so admin.useAsTitle: 'name' works out of the box
phoneTextPhone number (max 50 chars)
notesTextareaInternal notes visible only to admins
bookingsJoinVirtual field — all reservations for this customer

Reservations

Slug: reservations

The core booking records. Each reservation links a service performed by a resource to either a registered customer or an anonymous guest.

FieldTypeRequiredDescription
serviceRelationshipYesService being booked
resourceRelationshipYesResource performing the service
customerRelationshipNoCustomer making the booking. Optional — bookings may instead capture an anonymous guest. Uses a custom CustomerField admin component with inline create/edit
guestGroupNoAnonymous guest details: name (text, max 200), email (email), phone (text, max 50). Used when no customer is set
cancellationTokenTextNoHidden, indexed token for self-service (guest) cancellation. Read access restricted to non-customer users (admins/staff); never returned by the public API
startTimeDateYesAppointment start (availability-aware slot picker)
endTimeDateNoAuto-calculated from service duration (read-only)
statusSelectNoWorkflow status (default: 'pending')
guestCountNumberNoNumber of guests (default: 1, min: 1)
cancellationReasonTextareaNoVisible only when status is 'cancelled'
notesTextareaNoAdditional notes
itemsArrayNoMulti-resource booking lines. Each item: resource (relationship, required), service (relationship, optional), startTime (date, optional), endTime (date, optional), guestCount (number, optional, min 1). Missing values inherit the parent reservation
idempotencyKeyTextNoUnique key to prevent duplicate submissions (sidebar, read-only)

A reservation must have either a customer or a guest — not both, not neither (enforced by the validateGuestBooking hook, gated by allowGuestBooking). See Booking Features → Multi-Resource Bookings for the items array and Booking Features → Guest Bookings for account-less bookings.

Edit on GitHub

On this page