Booking App Guide · Bubble.io

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.

AvailabilityLogic Explained
No DoubleBooking
StripeOn Booking
⏱ 12 min read · Bubble.io · 2026

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

👤Provider
user→ User
nametext
biotext
slugtext (unique)
timezonetext
buffer_minutesnumber

🕑AvailabilityRule
provider→ Provider
day_of_weekoption set
start_timetext (HH:MM)
end_timetext (HH:MM)
is_activeyes/no

📌Appointment
provider→ Provider
client→ User
service→ Service
start_timedate
end_timedate
statusoption set
stripe_pi_idtext

🏠Service
provider→ Provider
nametext
duration_minnumber
pricenumber
descriptiontext

🚫BlockedTime
provider→ Provider
startdate
enddate
reasontext

Review
appointment→ Appointment
ratingnumber
bodytext
created_datedate

Client Booking — Step by Step

1
Client selects a service and a date

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.

2
Client selects a time slot

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.

3
Create a Pending appointment and charge via Stripe
Step 1: Create Appointment: status = Pending
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
4
Webhook confirms — status becomes Confirmed

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.

Book a Free Call →See Our Work

Simple Automation Solutions

Business Process Automation, Technology Consulting for Businesses, IT Solutions for Digital Transformation and Enterprise System Modernization, Web Applications Development, Mobile Applications Development, MVP Development

Copyright © 2026