How to Build a Booking & Scheduling App on Bubble.io
Calendly-style scheduling, service marketplaces, appointment booking systems — all achievable in Bubble with the right data model and workflow architecture. This guide covers availability logic, double-booking prevention, and payment on booking.
The Four Pillars of a Scheduling App in Bubble
Booking and scheduling apps are among the most requested Bubble project types — and among the most frequently built incorrectly. The common mistake is treating availability as a static list of time slots stored in the database. The correct approach treats availability as a calculated output: what slots exist, minus what is already booked, equals what is bookable. That calculation runs in Bubble workflows, not in a spreadsheet of pre-created slots.
Availability Rules
Provider defines their recurring weekly schedule — Monday 9am–5pm, Tuesday 9am–1pm, etc. Store as an AvailabilityRule data type with day, start time, and end time fields. Not individual slots.
Blocked Times
Provider blocks specific dates or time ranges — holidays, personal appointments, manual holds. A BlockedTime data type with start and end datetime. Subtract these from available slots on any given day.
Appointments
The Appointment record is the booking itself — provider, client, service, start_time, end_time, status. Confirmed appointments also subtract from availability to prevent double-booking.
Payment on Booking
Stripe Payment Intent created at booking time. Appointment status = Pending until payment confirmed by webhook. If payment fails, slot is released. This prevents free slot-holding without payment.
Confirmation Emails
Triggered by the payment webhook — not the booking workflow. SendGrid template with appointment details, provider contact, and calendar attachment (iCal link generated via API). Sent to both provider and client.
Reminders
Scheduled backend workflows firing 24h and 1h before the appointment start_time. Checks appointment status is still Confirmed before sending. Dramatically reduces no-show rates.
The Scheduling Data Model
Client Booking — Step by Step
Display the provider’s services from the Service data type. Date picker lets the client choose a day. On date selection, a backend workflow calculates available slots for that day by cross-referencing AvailabilityRules, BlockedTimes, and existing Confirmed Appointments.
Display calculated slots as buttons. Each slot shows the start time. Slots that overlap with existing appointments or blocked times are excluded. The slot duration equals the selected service’s duration_min field.
Step 2: POST stripe.com/v1/payment_intents
amount = Service’s price in cents
metadata[appointment_id] = Appointment Unique ID
Step 3: Update Appointment: stripe_pi_id = response.id
Step 4: Confirm payment via Stripe Elements in browser
On payment_intent.succeeded: find Appointment by metadata.appointment_id, set status = Confirmed, send confirmation emails to both parties, schedule reminder workflows for 24h and 1h before start_time.
Always Use Provider Timezones
Store all appointment times in UTC in the database. Display converted to the viewer’s local timezone using Bubble’s :formatted as with timezone parameter. If the provider is in London and the client is in New York, both see the correct local time from the same UTC database value.
Ready to Build on Bubble?
Architecture, data model design, Stripe billing, and full SaaS builds — done right from day one.
