AgentSkillsCN

payload-reserve

Payload CMS 3.x 预订/预约系统的 payload-reserve 插件专家指南。适用于以下场景:预订系统、预约系统、预约日程安排、日历视图、可用性查询、冲突检测、双重预约预防、状态流转(待处理/已确认/已完成/已取消/未到场)、缓冲时间、取消政策、日程管理、服务/资源配置、客户管理、即时预约,或与 Payload CMS 预订插件集成支付(Stripe)与通知功能时使用。可通过“payload-reserve”、“payloadReserve”、“预订插件”、“预约插件”、“预约插件”、“日程插件”、“可用性概览”、“日历视图”、“预订冲突”、“双重预约”、“预约状态”、“取消政策”等短语触发。

SKILL.md
--- frontmatter
name: payload-reserve
description: >
  Expert guide for the payload-reserve plugin — a Payload CMS 3.x reservation/booking system.
  Use when working with: reservation systems, booking systems, appointment scheduling,
  calendar views, availability checks, conflict detection, double-booking prevention,
  status workflows (pending/confirmed/completed/cancelled/no-show), buffer times,
  cancellation policies, schedule management, service/resource configuration,
  customer management, walk-in bookings, or integrating payments (Stripe) and
  notifications with a Payload CMS reservation plugin.
  Triggers on: "payload-reserve", "payloadReserve", "reservation plugin", "booking plugin",
  "appointment plugin", "schedule plugin", "availability overview", "calendar view",
  "reservation conflict", "double booking", "booking status", "cancellation policy".

payload-reserve Plugin Guide

Overview

payload-reserve is a Payload CMS 3.x plugin that injects a complete reservation/booking system:

  • 4 collections: Services, Resources, Schedules, Reservations
  • User extension: Appends customer fields (name, phone, notes, bookings join) to an existing auth collection
  • 4 beforeChange hooks: Auto endTime calculation, conflict detection, status state machine, cancellation policy
  • Admin components: Dashboard widget (RSC), Calendar view (client), Customer picker (client), Availability grid (client)
  • Custom endpoint: /api/reservation-customer-search for multi-field customer search

Plugin pattern: Higher-order function (pluginOptions) => (config) => modifiedConfig.

Three export paths:

  • payload-reserve — server-side plugin function, types, and utility functions
  • payload-reserve/client — CalendarView, AvailabilityOverview, CustomerField
  • payload-reserve/rsc — DashboardWidgetServer

Quick Start

ts
import { buildConfig } from 'payload'
import { payloadReserve } from 'payload-reserve'

export default buildConfig({
  collections: [/* must include a 'users' auth collection */],
  plugins: [payloadReserve()],
})

Gotcha: The users collection (or whichever userCollection you specify) must be defined in config.collections before the plugin runs. The plugin finds it and appends fields.

Peer dependency: payload ^3.76.1

Configuration

All options are optional — the plugin works out of the box.

ts
payloadReserve({
  disabled: false,                    // disable plugin (collections still registered)
  slugs: {
    services: 'services',             // override collection slugs
    resources: 'resources',
    schedules: 'schedules',
    reservations: 'reservations',
    media: 'media',                   // media collection for Resources image field
  },
  userCollection: 'users',           // existing auth collection to extend
  adminGroup: 'Reservations',        // admin panel group name
  defaultBufferTime: 0,              // minutes between reservations (fallback)
  cancellationNoticePeriod: 24,      // minimum hours notice for cancellation
  customerRole: false,               // filter customers by role (string or false)
  access: {                          // per-collection access control overrides
    services: { read: () => true },
    resources: { /* ... */ },
    schedules: { /* ... */ },
    reservations: { /* ... */ },
  },
})
OptionDefaultDescription
disabledfalseDisable plugin functionality
slugs.*services, resources, schedules, reservations, mediaCollection slugs
userCollection'users'Auth collection to extend with customer fields
adminGroup'Reservations'Admin panel group
defaultBufferTime0Default buffer minutes between bookings
cancellationNoticePeriod24Minimum hours notice for cancellation
customerRolefalseFilter customers by role in reservation form
access{}Per-collection access control overrides

Collection Relationships

code
Services <--many-to-many-- Resources
                              |
                         has schedule
                              |
                          Schedules

Reservations --> Service
             --> Resource
             --> Customer (User)
  • Resources reference Services (hasMany) — which services a resource can perform
  • Schedules belong to a Resource — when the resource is available
  • Reservations reference a Service, Resource, and User (customer)

For full field schemas, see references/collections.md.

Status State Machine

code
              +-> confirmed --+-> completed
              |               |
pending ------+               +-> cancelled
              |               |
              +-> cancelled   +-> no-show
  • On create (public): Must be pending
  • On create (admin): Can be pending or confirmed (walk-in support)
  • Terminal states (completed, cancelled, no-show): No further transitions

For hook details, conflict detection algorithm, cancellation policy, and escape hatch, see references/hooks-and-status.md.

Customization Patterns

Adding Fields After Plugin

Add fields to plugin collections after the plugin runs using another plugin or config manipulation:

ts
export default buildConfig({
  plugins: [
    payloadReserve(),
    // Add fields to reservations after the plugin
    (config) => {
      const reservations = config.collections?.find(c => c.slug === 'reservations')
      if (reservations) {
        reservations.fields.push({ name: 'internalNotes', type: 'textarea' })
      }
      return config
    },
  ],
})

Custom afterChange Hooks

Add hooks to plugin collections for notifications, integrations, etc.:

ts
// In a plugin that runs after payloadReserve()
const reservations = config.collections?.find(c => c.slug === 'reservations')
if (reservations) {
  if (!reservations.hooks) reservations.hooks = {}
  if (!reservations.hooks.afterChange) reservations.hooks.afterChange = []
  reservations.hooks.afterChange.push(myNotificationHook)
}

Access Control for Public Booking

Pass access overrides in plugin config to enable public booking. See references/frontend-booking.md for a complete step-by-step guide.

Custom Slug Example

ts
payloadReserve({
  slugs: {
    services: 'salon-services',
    resources: 'stylists',
    schedules: 'stylist-schedules',
    reservations: 'appointments',
  },
})

Components access slugs via config.admin.custom.reservationSlugs.

Admin Components

  • DashboardWidget (RSC): Today's stats (total, upcoming, completed, cancelled, next appointment). Registered as modular dashboard widget with slug reservation-todays-reservations.
  • CalendarView (Client): Month/week/day CSS Grid calendar replacing Reservations list view. Color-coded by status. Click-to-create, event tooltips, current time indicator.
  • CustomerField (Client): Rich customer picker with multi-field search (name, phone, email), inline create/edit via document drawer, optional role filtering.
  • AvailabilityOverview (Client): Weekly resource availability grid at /admin/reservation-availability. Green=available, blue=booked, gray=exception.

Utility Exports

Available from payload-reserve (server-side):

FunctionPurpose
addMinutes(date, minutes)Add minutes to a Date
doRangesOverlap(startA, endA, startB, endB)Check time range overlap
computeBlockedWindow(start, end, bufferBefore, bufferAfter)Compute blocked window with buffers
hoursUntil(futureDate, now?)Hours between now and future date
resolveScheduleForDate(schedule, date)Resolve concrete time ranges for a date
combineDateAndTime(date, time)Merge date + "HH:mm" string
isExceptionDate(date, exceptions)Check if date is an exception

Troubleshooting

"Could not find collection 'users' to extend": The users collection must be defined in config.collections before the plugin runs. Ensure your Users collection is listed before calling payloadReserve().

Conflict detection not catching overlaps: Check that bufferTimeBefore/bufferTimeAfter are set on the Service. The plugin falls back to defaultBufferTime (default: 0) if not set.

Status transition errors: Only valid transitions are allowed. Check the state machine diagram above. Use context: { skipReservationHooks: true } for migrations/seeding.

Cancellation rejected: The cancellationNoticePeriod (default: 24h) blocks late cancellations. Use the escape hatch for automated cleanup.

Customer picker not filtering by role: Set customerRole: 'your-role' in plugin config. Your user collection must have a role field — the plugin doesn't add it.

endTime not updating: endTime is auto-calculated from startTime + service.duration. Ensure the service has a duration value. The field is read-only.

Deep Dives