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… |
|---|---|
customdatepicker | Simple date/time picking with no slot capacity logic |
customcascadingdropdowns | Generic hierarchical dropdown not backed by schedules |
Props
| Prop | Type | Required? | Description |
|---|---|---|---|
label | string | required | Visible label for the overall widget. |
required | boolean | required | Always false. The expression controls the runtime value. |
defaultRequired | boolean | required | true for required fields, false for optional ones. |
scheduleId | string | required | UUID of the schedule that supplies group types, groups, and slots. Set in the portal settings panel or here directly. |
configs | array | required | Fixed array of exactly 4 config objects — one per step. See below. |
hideField | boolean | optional | Hides 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:
| Index | Step | isSeats | Notes |
|---|---|---|---|
| 0 | Group type | — | Root dropdown; no parent. Fetches from the schedule's group-types API. |
| 1 | Group | — | Depends on index 0. Fetches groups for the chosen group type. |
| 2 | Time slot | — | Depends on index 1. Fetches available slots for the chosen group. |
| 3 | Seats | true | Depends on index 2. Number input; user enters how many seats to book. |
Shared config fields (indexes 0–2, dropdowns)
| Field | Type | Required? | Description |
|---|---|---|---|
key | string | required | UPPER_SNAKE_CASE key. Each config must have a unique key. |
label | string | required | Visible label for this step's input. |
parent | string | optional | key of the preceding config. Absent on index 0 (root); required on 1 and 2. |
required | boolean | required | Always true for all four steps. |
bindlabel | string | required | Key in each option object to display. "name" for group type and group; "label" for time slot. |
bindvalue | string | required | Key in each option object to store. "code" for group type and group; "slotId" for time slot. |
placeholder | string | optional | Ghost text shown before the user picks. |
requiredErrorMessage | string | optional | Validation message when the step is skipped. |
Seats config fields (index 3)
All fields above except bindlabel/bindvalue, plus:
| Field | Type | Required? | Description |
|---|---|---|---|
isSeats | boolean | required | Must be true. Tells the runtime this step is a number input. |
maxSeats | number | optional | Client-side cap on the number of seats. Enforced before submission. |
defaultValue | number | optional | Pre-filled seat count. |
hidden | boolean | optional | Hides the seats step entirely (e.g. when seats are not applicable). |
maxSeatErrorMessage | string | optional | Shown when the count exceeds maxSeats. |
minSeatErrorMessage | string | optional | Shown when the count is less than 1. |
numbersOnlyErrorMessage | string | optional | Shown when the input is not a valid number. |
validation.messages
| Key | Description |
|---|---|
required | Shown when the overall widget is required and not completed. |
invalidSlot | Shown 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:
| Step | Endpoint |
|---|---|
| Group type | GET /service/api/v1/schedules/{scheduleId}/group-types |
| Slot | GET /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
-
keyisUPPER_SNAKE_CASEand unique across the entire form -
props.labelis present -
props.scheduleIdis set to the correct schedule UUID -
required: falseis set (nevertrue) -
defaultRequiredis set -
expressions["props.required"]is present with the exact required expression -
configshas exactly 4 entries in order: group type, group, time slot, seats - Index 0 has no
parent; indexes 1, 2, 3 each haveparentpointing to the preceding config'skey - 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.requiredis present -
validation.messages.invalidSlotis present