Skip to main content

Best Boilerplates for Appointment Scheduling SaaS 2026

·StarterPick Team
appointment-schedulingcalendly-clonebookingsaas-boilerplatecal-com2026

Scheduling SaaS Is a Proven Niche

Appointment scheduling software — Calendly, Cal.com, Acuity — is a large, proven market. Every professional service business needs booking software: coaches, consultants, doctors, lawyers, tutors, barbers, personal trainers.

Building a scheduling SaaS product means:

  • Availability management — providers set their available hours
  • Calendar integration — sync with Google Calendar, Outlook, Apple Calendar
  • Booking flows — customers pick a time, receive confirmation
  • Buffer time — automatic gaps between appointments
  • Timezone handling — every interaction must be timezone-aware
  • Payment at booking — Stripe integration for paid consultations
  • Reminders — email and SMS before appointments

The hardest parts: timezone handling, calendar sync, and availability conflict detection.

TL;DR

Best starting points for appointment scheduling SaaS in 2026:

  1. Cal.com (open source, fork) — The most complete open-source scheduling system. MIT licensed, fork and white-label.
  2. Calcom atoms + Next.js — Embed Cal.com's scheduling UI as React components in your own SaaS.
  3. Nylas API + any SaaS boilerplate — Managed calendar integration (Google, Outlook, iCal).
  4. Google Calendar API + OpenSaaS — DIY scheduling with Google Calendar sync.
  5. Custom: Next.js + Prisma + date-fns-tz — Full control, timezone-safe implementation.

Key Takeaways

  • Cal.com is MIT licensed — you can fork it, white-label it, and sell it as your own product
  • Cal.com Atoms (their embeddable React components) let you add scheduling UI to any app
  • Nylas handles Google Calendar, Outlook, and Apple Calendar sync under one API
  • All scheduling logic must use UTC storage with timezone-aware display — never store local times
  • Buffer time prevents back-to-back bookings — implement as a constraint in availability checking
  • The average custom scheduling implementation takes 3-4 weeks; Cal.com fork is 1-2 weeks

Option 1: Fork Cal.com

Cal.com is the open-source Calendly alternative. Its stack: Next.js 15, Prisma, tRPC, Turborepo. MIT licensed.

git clone https://github.com/calcom/cal.com.git my-scheduler
cd my-scheduler

# Install dependencies:
yarn install

# Setup database:
cp .env.example .env
# Fill in: DATABASE_URL, NEXTAUTH_SECRET, GOOGLE_CLIENT_ID, etc.

yarn workspace @calcom/prisma db:push
yarn dev

Cal.com includes out of the box:

  • Availability management with recurring schedules
  • Google Calendar, Outlook, Apple Calendar bidirectional sync
  • Booking page with timezone detection
  • Email confirmations and reminders
  • Stripe payment integration at booking
  • Team scheduling (round robin, collective)
  • Event types (one-on-one, group, recurring)
  • Webhooks for booking events
  • API for programmatic booking

White-labeling: Replace Cal.com branding in packages/ui and deploy under your domain.

Customization effort: 2-4 weeks to white-label and customize for a specific vertical.


Option 2: Cal.com Atoms (Embed Scheduling in Your SaaS)

Cal.com Atoms are React components that embed scheduling UI into your application:

npm install @calcom/atoms
// Embed the scheduler in your Next.js app:
import { CalProvider, Booker } from '@calcom/atoms';

export function SchedulingPage({ userId }: { userId: string }) {
  return (
    <CalProvider
      clientId={process.env.NEXT_PUBLIC_CAL_CLIENT_ID!}
      options={{
        apiUrl: 'https://api.cal.com/v2',
        refreshUrl: '/api/cal/refresh',
      }}
    >
      <Booker
        username="your-cal-username"
        eventSlug="30min"
        onCreateBooking={(booking) => {
          console.log('Booking created:', booking);
          // Update your database, send custom email, etc.
        }}
      />
    </CalProvider>
  );
}

This approach: use any SaaS boilerplate (ShipFast, OpenSaaS) for auth/billing, embed Cal.com Atoms for the scheduling UI.

Trade-off: You are tied to Cal.com's infrastructure. Works well for adding scheduling to an existing SaaS; less appropriate for a scheduling-first product.


Option 3: Nylas API (Calendar Integration Layer)

Nylas provides a unified API for Google Calendar, Microsoft Outlook, and Apple iCloud:

npm install nylas
import Nylas from 'nylas';

const nylas = new Nylas({ apiKey: process.env.NYLAS_API_KEY! });

// Get user's availability:
export async function getAvailability(
  nylasGrantId: string,  // User's connected calendar grant
  startTime: Date,
  endTime: Date,
  durationMinutes: number
) {
  const availability = await nylas.calendars.getFreeBusy({
    identifier: nylasGrantId,
    requestBody: {
      start_time: Math.floor(startTime.getTime() / 1000),
      end_time: Math.floor(endTime.getTime() / 1000),
      emails: [userEmail],
    },
  });

  return availability;
}

// Create a calendar event when booking is confirmed:
export async function createBookingEvent(
  nylasGrantId: string,
  booking: {
    title: string;
    startTime: Date;
    endTime: Date;
    attendeeEmail: string;
    meetingUrl: string;
  }
) {
  const event = await nylas.events.create({
    identifier: nylasGrantId,
    requestBody: {
      title: booking.title,
      when: {
        start_time: Math.floor(booking.startTime.getTime() / 1000),
        end_time: Math.floor(booking.endTime.getTime() / 1000),
      },
      participants: [
        { email: booking.attendeeEmail, status: 'noreply' },
      ],
      conferencing: { details: { url: booking.meetingUrl } },
    },
    queryParams: { calendarId: 'primary', notify_participants: true },
  });

  return event;
}

Nylas pricing starts at $0 for development; $25/mo for production. It removes the calendar integration complexity entirely.


Building Custom: Availability Algorithm

If building from scratch, the availability algorithm:

// lib/availability.ts
import { addMinutes, isWithinInterval, startOfDay, endOfDay } from 'date-fns';
import { fromZonedTime, toZonedTime, format } from 'date-fns-tz';

interface WorkingHours {
  dayOfWeek: number;  // 0 = Sunday, 6 = Saturday
  startTime: string;  // "09:00"
  endTime: string;    // "17:00"
  timezone: string;   // "America/New_York"
}

interface ExistingBooking {
  startTime: Date;
  endTime: Date;
}

export function getAvailableSlots(
  workingHours: WorkingHours[],
  existingBookings: ExistingBooking[],
  date: Date,
  slotDurationMinutes: number,
  bufferMinutes: number,
  viewerTimezone: string
): Date[] {
  const dayOfWeek = date.getDay();
  const scheduleForDay = workingHours.find((wh) => wh.dayOfWeek === dayOfWeek);

  if (!scheduleForDay) return [];

  // Convert working hours to UTC:
  const [startHour, startMin] = scheduleForDay.startTime.split(':').map(Number);
  const [endHour, endMin] = scheduleForDay.endTime.split(':').map(Number);

  const localDate = toZonedTime(date, scheduleForDay.timezone);
  const workStart = fromZonedTime(
    new Date(localDate.getFullYear(), localDate.getMonth(), localDate.getDate(), startHour, startMin),
    scheduleForDay.timezone
  );
  const workEnd = fromZonedTime(
    new Date(localDate.getFullYear(), localDate.getMonth(), localDate.getDate(), endHour, endMin),
    scheduleForDay.timezone
  );

  const slots: Date[] = [];
  let currentSlot = workStart;

  while (addMinutes(currentSlot, slotDurationMinutes) <= workEnd) {
    const slotEnd = addMinutes(currentSlot, slotDurationMinutes);
    const slotWithBuffer = addMinutes(currentSlot, slotDurationMinutes + bufferMinutes);

    // Check if slot overlaps with any existing booking:
    const isBooked = existingBookings.some((booking) =>
      isWithinInterval(currentSlot, {
        start: addMinutes(booking.startTime, -bufferMinutes),
        end: booking.endTime,
      }) ||
      isWithinInterval(slotEnd, {
        start: booking.startTime,
        end: addMinutes(booking.endTime, bufferMinutes),
      })
    );

    if (!isBooked) {
      slots.push(currentSlot);
    }

    currentSlot = addMinutes(currentSlot, slotDurationMinutes + bufferMinutes);
  }

  return slots;
}

Timezone-Safe Storage

The critical rule: always store times in UTC, display in user timezone:

// API route: create booking
export async function POST(req: Request) {
  const { startTimeUTC, durationMinutes, attendeeTimezone } = await req.json();

  const startTime = new Date(startTimeUTC);  // UTC from client
  const endTime = addMinutes(startTime, durationMinutes);

  const booking = await db.booking.create({
    data: {
      startTime,  // UTC in database
      endTime,    // UTC in database
      attendeeTimezone,  // Store for display purposes
    },
  });

  // Send confirmation email with timezone-correct time:
  const displayTime = format(
    toZonedTime(startTime, attendeeTimezone),
    "EEEE, MMMM d 'at' h:mm a zzz",
    { timeZone: attendeeTimezone }
  );

  await sendConfirmationEmail({ time: displayTime });
}

Payment at Booking (Stripe)

// Create payment intent when booking is initiated:
export async function initiateBooking(
  serviceId: string,
  slotTime: Date,
  attendeeEmail: string
) {
  const service = await db.service.findUnique({ where: { id: serviceId } });

  if (service.price === 0) {
    // Free booking — skip payment:
    return createConfirmedBooking(serviceId, slotTime, attendeeEmail);
  }

  // Paid booking — create payment intent:
  const paymentIntent = await stripe.paymentIntents.create({
    amount: service.price,
    currency: 'usd',
    metadata: { serviceId, slotTime: slotTime.toISOString(), attendeeEmail },
  });

  // Create pending booking (confirmed on payment success):
  const booking = await db.booking.create({
    data: {
      serviceId,
      startTime: slotTime,
      attendeeEmail,
      status: 'PENDING_PAYMENT',
      stripePaymentIntentId: paymentIntent.id,
    },
  });

  return { clientSecret: paymentIntent.client_secret, bookingId: booking.id };
}

Scheduling ProductStarting Point
Calendly cloneFork Cal.com
Scheduling feature in SaaSCal.com Atoms
Multi-calendar syncNylas API + ShipFast
Niche vertical schedulerOpenSaaS + custom availability
Healthcare schedulingCustom (HIPAA compliance required)

Methodology

Based on publicly available information from Cal.com documentation, Nylas API documentation, and scheduling builder community resources as of March 2026.


Building a scheduling SaaS? StarterPick helps you find the right SaaS boilerplate to build your booking product on top of.

Comments