Components

customslotbooking

A slot booking widget that guides the user through a cascading selection of group type → group → time slot → number of seats, fetching available options from a schedule.

A slot booking widget that guides the user through a four-step cascading selection: group type → group → time slot → number of seats. Each step is unlocked only after the previous one is filled, and available options are fetched live from a schedule defined in the platform.

Read Form Rules before using this component — keys, labels, required fields, expressions, visibility, and validation all follow shared conventions.

When to use

  • Appointment booking where users must pick a group type, group, and time slot before reserving a seat
  • Any service backed by the platform's schedule/slot API (/service/api/v1/schedules/{scheduleId}/...)

When NOT to use

Instead of customslotbooking, use…For…
customdatepickerSimple date/time picking with no slot capacity logic
customcascadingdropdownsGeneric hierarchical dropdown not backed by schedules

Props

PropTypeRequired?Description
labelstringrequiredVisible label for the overall widget.
requiredbooleanrequiredAlways false. The expression controls the runtime value.
defaultRequiredbooleanrequiredtrue for required fields, false for optional ones.
scheduleIdstringrequiredUUID of the schedule that supplies group types, groups, and slots. Set in the portal settings panel or here directly.
configsarrayrequiredFixed array of exactly 4 config objects — one per step. See below.
hideFieldbooleanoptionalHides the entire widget. Toggle via expressions["props.hideField"].

configs array — the four fixed steps

configs is always an array of exactly 4 objects, in this order:

IndexStepisSeatsNotes
0Group typeRoot dropdown; no parent. Fetches from the schedule's group-types API.
1GroupDepends on index 0. Fetches groups for the chosen group type.
2Time slotDepends on index 1. Fetches available slots for the chosen group.
3SeatstrueDepends on index 2. Number input; user enters how many seats to book.

Shared config fields (indexes 0–2, dropdowns)

FieldTypeRequired?Description
keystringrequiredUPPER_SNAKE_CASE key. Each config must have a unique key.
labelstringrequiredVisible label for this step's input.
parentstringoptionalkey of the preceding config. Absent on index 0 (root); required on 1 and 2.
requiredbooleanrequiredAlways true for all four steps.
bindlabelstringrequiredKey in each option object to display. "name" for group type and group; "label" for time slot.
bindvaluestringrequiredKey in each option object to store. "code" for group type and group; "slotId" for time slot.
placeholderstringoptionalGhost text shown before the user picks.
requiredErrorMessagestringoptionalValidation message when the step is skipped.

Seats config fields (index 3)

All fields above except bindlabel/bindvalue, plus:

FieldTypeRequired?Description
isSeatsbooleanrequiredMust be true. Tells the runtime this step is a number input.
maxSeatsnumberoptionalClient-side cap on the number of seats. Enforced before submission.
defaultValuenumberoptionalPre-filled seat count.
hiddenbooleanoptionalHides the seats step entirely (e.g. when seats are not applicable).
maxSeatErrorMessagestringoptionalShown when the count exceeds maxSeats.
minSeatErrorMessagestringoptionalShown when the count is less than 1.
numbersOnlyErrorMessagestringoptionalShown when the input is not a valid number.

validation.messages

KeyDescription
requiredShown when the overall widget is required and not completed.
invalidSlotShown when the chosen slot is no longer available at submission time.

APIs called at runtime

The widget calls the schedule API under the hood — the scheduleId prop is substituted into the paths:

StepEndpoint
Group typeGET /service/api/v1/schedules/{scheduleId}/group-types
SlotGET /service/api/v1/schedules/{scheduleId}/slots

Groups and available seats are derived from the group-types and slots responses.

Example

{
  "key": "SLOT_BOOKING",
  "type": "customslotbooking",
  "props": {
    "label": "Book an appointment",
    "required": false,
    "defaultRequired": true,
    "scheduleId": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
    "configs": [
      {
        "key": "SLOT_GROUP_TYPE",
        "label": "Group Type",
        "required": true,
        "bindlabel": "name",
        "bindvalue": "code",
        "placeholder": "Select a group type",
        "requiredErrorMessage": "Group type is required."
      },
      {
        "key": "SLOT_GROUP",
        "label": "Group",
        "parent": "SLOT_GROUP_TYPE",
        "required": true,
        "bindlabel": "name",
        "bindvalue": "code",
        "placeholder": "Select a group",
        "requiredErrorMessage": "Group is required."
      },
      {
        "key": "SLOT_TIME_RANGE",
        "label": "Time Slot",
        "parent": "SLOT_GROUP",
        "required": true,
        "bindlabel": "label",
        "bindvalue": "slotId",
        "placeholder": "Select a time slot",
        "requiredErrorMessage": "Time slot selection is required."
      },
      {
        "key": "SLOT_SEATS",
        "label": "Number of Seats",
        "parent": "SLOT_TIME_RANGE",
        "isSeats": true,
        "required": true,
        "placeholder": "Enter number of seats",
        "maxSeats": 5,
        "requiredErrorMessage": "Number of seats is required.",
        "maxSeatErrorMessage": "Exceeds the maximum allowed seats for this slot.",
        "minSeatErrorMessage": "Minimum of 1 seat required.",
        "numbersOnlyErrorMessage": "Please enter a valid whole number."
      }
    ]
  },
  "expressions": {
    "props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
  },
  "validation": {
    "messages": {
      "required": "This field is required.",
      "invalidSlot": "The selected slot is no longer available. Please choose another."
    }
  }
}

Checklist

  • key is UPPER_SNAKE_CASE and unique across the entire form
  • props.label is present
  • props.scheduleId is set to the correct schedule UUID
  • required: false is set (never true)
  • defaultRequired is set
  • expressions["props.required"] is present with the exact required expression
  • configs has exactly 4 entries in order: group type, group, time slot, seats
  • Index 0 has no parent; indexes 1, 2, 3 each have parent pointing to the preceding config's key
  • Indexes 0 and 1 use bindlabel: "name", bindvalue: "code"
  • Index 2 uses bindlabel: "label", bindvalue: "slotId"
  • Index 3 has isSeats: true
  • All four configs have required: true
  • validation.messages.required is present
  • validation.messages.invalidSlot is present

On this page