Build an appointment scheduling system using Firestore collections for providers (with working hours and specialties), appointments (with dateTime, duration, and status), and services. Users select a provider, pick a date with FlutterFlow's DateTimePicker, see available time slots calculated from the provider's working hours minus existing bookings, and book by creating an appointment document. Include confirmation notifications and a 24-hour cancellation policy check.
Provider-based scheduling with available time slots and booking management
An appointment scheduling system lets users pick a provider, choose an available time slot, and book. This tutorial builds one in FlutterFlow using Firestore for data storage, FlutterFlow's built-in DateTimePicker for date selection (no custom calendar widget needed), and a Custom Function to calculate available slots by subtracting booked times from the provider's working hours. The system includes confirmation, cancellation with a 24-hour policy, and a provider dashboard for managing upcoming appointments.
Prerequisites
- A FlutterFlow project with Firebase/Firestore connected
- Firebase Authentication enabled with user sign-in working
- Basic understanding of Backend Queries, Custom Functions, and Action Flows
- Cloud Functions enabled for confirmation notifications (optional)
Step-by-step guide
Create the Firestore data model for providers, services, and appointments
Create the Firestore data model for providers, services, and appointments
Create a providers collection with fields: userId (String), displayName (String), avatarUrl (String), specialty (String: 'Doctor', 'Dentist', 'Therapist', etc.), rating (Double), workingHours (Map: {startHour: 9, endHour: 17, daysOfWeek: [1,2,3,4,5]} where 1=Monday, 7=Sunday), and slotDurationMinutes (Integer: 30 or 60). Create a services collection with fields: name (String), durationMinutes (Integer), price (Double), and providerId (String). Create an appointments collection with fields: userId (String), providerId (String), serviceId (String), dateTime (Timestamp), duration (Integer, minutes), status (String: 'confirmed', 'cancelled', 'completed'), notes (String, optional), and createdAt (Timestamp). Set Firestore rules: users can read providers and services, users can read and create their own appointments, and providers can read and update appointments assigned to them.
Expected result: Firestore has providers, services, and appointments collections with working hours metadata and status tracking.
Build the provider selection page with ratings and specialties
Build the provider selection page with ratings and specialties
Create a ProvidersPage with a ListView bound to a Backend Query on the providers collection. Add a ChoiceChips filter for specialty (All, Doctor, Dentist, Therapist, etc.) and a sort DropDown (Rating, Name). Each provider card is a Component showing: CircleImage (avatar), displayName Text, specialty badge Container, rating displayed as stars (Row of 5 Icon widgets, filled or outlined based on rating value via Conditional Styling), and a Text showing available days. Tapping a provider navigates to the BookingPage passing the provider document reference. Add a search TextField for finding providers by name.
Expected result: Users see a filterable list of providers with ratings, specialties, and avatars. Tapping a provider starts the booking flow.
Calculate and display available time slots for a selected date
Calculate and display available time slots for a selected date
On the BookingPage, display the provider's info at the top. Add a DateTimePicker (date only mode) defaulting to today, with min date set to today and max date set to 30 days ahead. When a date is selected, create a Custom Function named getAvailableSlots that: (1) takes the provider's workingHours (startHour, endHour), slotDurationMinutes, and the selected date; (2) generates all possible slots from startHour to endHour in increments of slotDurationMinutes (e.g., 9:00, 9:30, 10:00, ... 16:30 for a 30-min slot with 9-17 hours); (3) queries existing appointments for this provider on the selected date with status != 'cancelled'; (4) removes booked slots from the list; (5) checks that the selected date's day-of-week is in the provider's daysOfWeek array. Display available slots as a Wrap widget of tappable Chip-style Containers, each showing the time (e.g., '10:30 AM'). Booked slots are greyed out or hidden. Tapping an available slot stores it in Page State selectedSlot.
Expected result: After selecting a date, available time slots appear as tappable chips. Booked and past slots are excluded.
Book the appointment and send confirmation
Book the appointment and send confirmation
Below the time slots, add a service DropDown (filtered by this providerId) and an optional notes TextField. Add a Book Appointment button that activates only when a date, time slot, and service are selected (Conditional Enabling). On Tap Action Flow: Create Document in appointments with userId (current user), providerId, serviceId, dateTime (combine selected date + selected slot time into a Timestamp), duration (from the service's durationMinutes), status 'confirmed', notes, and createdAt as server timestamp. After creation, show a confirmation BottomSheet displaying the appointment details: provider name, date, time, service, and a calendar-add suggestion. Optionally, call a Cloud Function to send a confirmation email or push notification to both the user and provider. Navigate back to a MyAppointmentsPage.
Expected result: Booking creates an appointment document and shows a confirmation. The slot is now taken and will not appear for other users.
Build the My Appointments page with cancellation policy
Build the My Appointments page with cancellation policy
Create a MyAppointmentsPage with two tabs: Upcoming and Past. Upcoming tab: ListView of appointments where userId equals current user, status equals 'confirmed', and dateTime is in the future, ordered by dateTime ascending. Each row shows: provider name, service name, date and time, and a Cancel button. The Cancel button's On Tap Action Flow: first check if the appointment dateTime is more than 24 hours from now using a Custom Function that compares dateTime - DateTime.now() > 24 hours. If yes, show a confirmation dialog, then update the appointment status to 'cancelled'. If under 24 hours, show a message: 'Appointments cannot be cancelled within 24 hours. Please contact the provider directly.' Past tab: appointments where dateTime is in the past, with a 'Leave Review' option. Providers see their own dashboard with a similar layout filtered by providerId.
Expected result: Users see their upcoming and past appointments. Cancellation is allowed only 24+ hours before the appointment time.
Complete working example
1Firestore Data Model:2├── providers/{providerId}3│ ├── userId: String4│ ├── displayName: String ("Dr. Sarah Chen")5│ ├── avatarUrl: String6│ ├── specialty: String ("Dentist")7│ ├── rating: Double (4.8)8│ ├── workingHours: Map9│ │ ├── startHour: Integer (9)10│ │ ├── endHour: Integer (17)11│ │ └── daysOfWeek: List<Integer> [1, 2, 3, 4, 5]12│ └── slotDurationMinutes: Integer (30)13├── services/{serviceId}14│ ├── name: String ("Dental Cleaning")15│ ├── durationMinutes: Integer (60)16│ ├── price: Double (150.00)17│ └── providerId: String18└── appointments/{appointmentId}19 ├── userId: String (patient)20 ├── providerId: String21 ├── serviceId: String22 ├── dateTime: Timestamp (2026-03-30 10:30)23 ├── duration: Integer (60 minutes)24 ├── status: String ("confirmed" | "cancelled" | "completed")25 ├── notes: String (optional)26 └── createdAt: Timestamp2728Booking Page:29├── Provider Info Card (avatar, name, specialty, rating)30├── DateTimePicker (date only, min: today, max: +30 days)31├── Wrap (available time slot chips)32│ └── Container (time chip, e.g., "10:30 AM")33│ ├── Available: tappable, theme primary outline34│ └── On Tap → Set Page State: selectedSlot35├── DropDown (service selection, filtered by provider)36├── TextField (notes, optional)37└── Button ("Book Appointment")38 └── On Tap → Create appointment doc → Confirmation BottomSheet3940Available Slots Calculation (Custom Function):411. Generate all slots: startHour to endHour, step slotDurationMinutes422. Query existing appointments for provider + date (status != cancelled)433. Remove booked slot times from generated list444. If date == today, remove slots in the past455. Return remaining available slots as List<String>4647My Appointments Page:48├── TabBar (Upcoming | Past)49├── Upcoming Tab50│ └── ListView (appointments, confirmed, future, orderBy dateTime ASC)51│ └── Row → Provider name + Service + Date/Time + Cancel button52│ └── Cancel: if > 24h away → update status → "cancelled"53└── Past Tab54 └── ListView (appointments, past dates)55 └── Row → Details + Leave Review buttonCommon mistakes when building an Appointment Scheduling System in FlutterFlow
Why it's a problem: Showing all 24 hours as available slots instead of filtering by the provider's working hours
How to avoid: Store working hours (startHour, endHour, daysOfWeek) on the provider document. The slot generation Custom Function only creates slots within these bounds. If the selected date's day-of-week is not in daysOfWeek, show 'Provider not available on this day'.
Why it's a problem: Not checking for double-booking when two users select the same slot simultaneously
How to avoid: Use a Firestore transaction in a Cloud Function for the booking: atomically check if an appointment already exists for the provider + dateTime, and only create the new one if the slot is still free. Return an error if the slot was taken.
Why it's a problem: Using a Timestamp comparison for slot matching without accounting for time zones
How to avoid: Convert all times to UTC before storing and querying. Display times in the user's local time zone using a Custom Function. Be consistent: always store UTC, always display local.
Best practices
- Store provider working hours on the provider document for dynamic slot generation
- Generate available slots by subtracting booked appointments from the provider's schedule
- Filter out past time slots when the selected date is today
- Use a Firestore transaction for booking to prevent double-booking race conditions
- Enforce cancellation policies with a time threshold check (24 hours) before allowing status changes
- Send confirmation notifications to both user and provider after booking
- Add a composite Firestore index on providerId + dateTime for efficient appointment queries
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write a Custom Function for FlutterFlow that generates available time slots for a provider given their working hours (start hour, end hour, slot duration) and a list of existing appointments on the selected date. Return only the unbooked slots.
Create a booking page with a provider info card at the top, a date picker, a grid of available time slot chips, a service dropdown, and a Book Appointment button. Available slots should update when the user changes the date.
Frequently asked questions
How is this different from the booking system with calendar integration tutorial?
The calendar integration tutorial uses a table_calendar Custom Widget with color-coded days showing availability. This tutorial uses FlutterFlow's simpler built-in DateTimePicker widget for date selection without a custom calendar, making it easier to implement but with less visual richness.
How do I send appointment reminders?
Create a scheduled Cloud Function that runs daily. Query appointments where dateTime is within the next 24 hours and status is confirmed. For each, create a notification document and optionally send a push notification via Firebase Cloud Messaging.
Can I add recurring appointments?
Yes. Add a recurrence field on the appointment (weekly, biweekly, monthly) and a Cloud Function that creates future appointment documents. When booking a recurring appointment, generate the next N occurrences and check each for conflicts before creating.
How do I handle providers with different slot durations per service?
Store durationMinutes on the service document instead of (or in addition to) the provider. When generating available slots, use the selected service's duration to determine slot length. A 30-minute cleaning and a 90-minute root canal would generate different slot patterns.
How do I allow providers to block off vacation days?
Create a provider_unavailability collection with fields providerId, startDate, endDate, and reason. When generating available slots, query this collection for the selected date. If the date falls within an unavailability range, show 'Provider unavailable' instead of time slots.
Can RapidDev help build a full scheduling platform?
Yes. A production scheduling platform needs Google Calendar sync, SMS reminders, waitlists, payment collection at booking, multi-location support, and no-show tracking. RapidDev can architect the complete system with integrations beyond what the visual builder provides.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation