Instructions
# Appointment Availability Builder
Build weekly hours and date exceptions visually, display available times in a visitor's time zone, and calculate the same schedule in a Bubble server workflow. Launch price: $15/month or $199 once per Bubble app. No separate hosted processing service or API key is required.
## Quick start
1. Install the plugin and place **Availability Builder** on a page. Start with a 900 × 900 element; its contents scroll inside the element.
2. Preview the page. Choose **From date** and **Through date**, a schedule time zone, and a viewer time zone. Dates are inclusive calendar dates in the schedule zone.
3. Open **Weekly working hours**. Add one or more windows for each working day. For example, Monday 15:30–18:30 with 30-minute duration and 30-minute start interval produces six choices before occupied times are excluded.
4. Use **Date overrides and closures** to replace that day's usual hours or close the date. Multiple open windows on one date are allowed; a date cannot be both open and closed.
5. Click **Calculate availability**. In the workflow event **A time is selected**, read the element's `selected start`, `selected end`, `selected id`, and `selected label` states.
Your app authorizes and saves appointment records. Calculation returns availability at calculation time; it does not acquire a lock or reserve capacity. If your application requires simultaneous booking protection, implement confirmation through your authoritative reservation system. You do not need another Bingo plugin to use the advertised calculation workflow.
## Exclude occupied appointments
Create an appointment type with Bubble **date** fields for start and end. On the element, choose **Occupied interval data type**, supply the authorized list of records, and select the start and end fields. Use privacy rules and search constraints appropriate to the schedule owner/resource. Changes to supplied appointments clear the old results and selection; calculate again to get current choices.
Intervals are half-open: an appointment ending at 16:00 does not block another starting at 16:00, unless your configured buffers overlap it. A record requires two valid dates and an end after its start. Do not supply local time text as occupied dates.
## Save and load a schedule without writing JSON
Create a text field such as `availability_schedule` on the schedule owner or schedule record.
**Save button workflow:** run **Save schedule configuration** on the element, then save its `configuration` state to the text field. Only perform the database write when `configuration is not empty`. The plugin validates the current controls before producing the configuration.
**Load button workflow:** when the stored field is not empty, run **Load saved schedule** using that text, then **Calculate availability** on the element. Alternatively bind **Initial saved configuration** to the stored field. Loading restores controls and clears previous results. A currently bound occupied-record list takes precedence over occupied intervals stored inside an older configuration.
The saved text uses a versioned interchange format. Store it intact; normal setup does not require editing it. It can include occupied timestamps, so apply the same access controls and retention policy you use for appointment data.
## Calculate in a server workflow
Add the server action **Calculate availability**. Choose either of these setup paths:
| Path | Configure |
| --- | --- |
| Saved visual schedule | Pass the element's or database field's configuration into **Saved configuration**. Optionally override From date, Through date, Viewer time zone, and native occupied records. |
| Native schedule records | Leave Saved configuration empty. Supply weekly records and field selectors, dated override records if needed, occupied records if needed, and schedule options. |
For native weekly records, use a **number** weekday (Monday 1 through Sunday 7) and **text** start/end fields (`HH:mm`). For dated overrides, use a **text** calendar date (`YYYY-MM-DD`), a **yes/no** closed field, and **text** start/end fields. Multiple open records for one date define separate windows; a closed record needs no start/end values. Only yes closes a date; no or an empty closed value means an open override and requires valid start/end text. With no weekly records and no matching open override, the date is closed.
Native server defaults are America/Chicago schedule zone, UTC viewer zone, 30-minute duration, 15-minute start interval, zero buffers, and the earlier repeated-time occurrence. From/Through dates must be supplied. A saved configuration owns the schedule zone, hours, duration, interval, buffers, and repeated-time policy; those native option fields do not override it.
Use IANA names such as `America/Chicago`, `Europe/London`, or `Asia/Tokyo`. Start times are local to the schedule zone. End `24:00` means the next midnight. Split windows crossing midnight into separate dates.
## Outputs, events, and actions
The server returns `success`, `slot count`, text lists `slot ids`, `labels`, and `warnings`, date lists `starts` and `ends`, a number list `offsets`, `configuration`, `error code`, and `error message`. Lists have matching positions; offsets are UTC offset minutes in the viewer zone. Always branch on `success` before processing results. Expected validation errors return `success = no` with empty result lists and a readable error.
The element publishes those result states except `success`, plus `status`, `selected id`, `selected start`, `selected end`, `selected label`, and `has selection`. Do not persist a booking unless `has selection` is yes. IDs distinguish repeated daylight-saving occurrences by their UTC instants.
| Element event | Use |
| --- | --- |
| The builder is ready | Initial element configuration and supplied data are ready. |
| Availability is calculated | Read all result lists together. |
| A time is selected | Read the selected identity and dates. |
| Selection is cleared | Clear any app-owned confirmation UI. |
| Configuration changes | Observe a validated schedule change/export; avoid automatically writing to the database on every edit. |
| An error occurs | Read error code/message and show your app's error handling. |
Element actions are **Calculate availability**, **Cancel calculation**, **Clear selection**, **Load saved schedule**, and **Save schedule configuration**. Cancellation stops the element's current worker calculation and retains the schedule. Changing controls, loading a schedule, or receiving changed occupied records invalidates old results. A canceled or superseded worker cannot publish a stale result. Cancellation does not stop a separate Bubble server workflow already submitted by your app.
## Time-zone behavior
Candidate start times follow wall-clock minutes in the schedule zone. Duration and buffers represent elapsed minutes. The entire appointment and its buffers must fit inside a working window and avoid supplied occupied intervals.
Nonexistent spring-forward start times are skipped with warnings. A working-window boundary that falls in the gap shifts forward by the gap, also with a warning. On fall-back, select earlier, later, or both occurrences. Working windows span their earliest start through latest end instants; repeated candidate starts follow the selected policy. Results are sorted by UTC time and labeled in the viewer zone.
## Limits and processing
| Input/output | Bound per calculation |
| --- | --- |
| Inclusive date range | 93 calendar dates |
| Weekly windows | 70, with at most 10 per weekday |
| Override dates/windows | 93 dates, at most 10 windows per date |
| Occupied intervals | 10,000 |
| Returned times | 10,000 |
| Duration, interval, buffers | Whole minutes, at most 1,440; duration/interval must be positive |
| Saved configuration | 2,000,000 characters |
| Visible time-choice buttons | 100 per page |
Browser calculation runs in a bundled worker. Server calculation runs inside the customer's Bubble backend using Plugin API v4; the editor runtime used for qualification is Node 22. Time-zone data comes from that runtime. Luxon 3.7.2 (MIT) is bundled; the plugin does not download a mutable shared runtime script. Bubble workload and application database operations remain part of your Bubble plan.
Empty availability is a successful zero-result calculation. Invalid dates, invalid zones, malformed intervals, incompatible fields, contradictory overrides, and limit violations produce actionable errors. A malformed draft does not retain valid-looking old choices. Bubble privacy rules can legitimately return an empty record list; ensure your authorized workflow can see the intended records.
## Troubleshooting and upgrades
If no times appear, check date range, weekday, override closures, elapsed duration plus buffers, and occupied search results. If a native action reports a field error, verify the field types and all selectors. If browser calculation cannot start, inspect the visible error and use the server action while diagnosing app security/content settings. Do not pass private schedule data through a public URL.
Version 1 configuration and generated Bubble identifiers are stable release interfaces. Keep a copy of saved schedules and the prior plugin version when upgrading. Rollback uses Bubble's prior released version and the archived source bundle; do not recreate actions or states with new IDs. Report the plugin version, sanitized options, expected UTC time, actual result, and error code when requesting support. Avoid sending customer appointment records.
Release status and demo links will be recorded with the marketplace submission; this document does not imply approval or a live listing.