Cal.diy is the community-driven, fully open-source scheduling platform — a fork of Cal.com with all enterprise and commercial code removed. It lets people book time with you (or your team) through a personal booking page, embed that booking flow into any website, and build scheduling directly into your own product through React components and a REST API. Everything in this repository is MIT-licensed, requires no license key, and is designed to run on infrastructure you control.
This guide covers everything you can do with Cal.diy as a user, self-hoster, or developer integrating scheduling into your own app. Architecture, deployment internals, the full configuration reference, and security hardening live in the companion Admin Guide.
Warning
Cal.diy is the open-source community edition and is recommended for personal, non-production use. Self-hosting requires comfort with server administration, database management, and securing sensitive data. For commercial, enterprise-ready scheduling infrastructure, use Cal.com instead.
Table of Contents
- What Is Cal.diy?
- The Four Products at a Glance
- Quick Start
- Installation
- First-Run Setup
- Part 1 — Using the Scheduling Web App
- Part 2 — Embedding Cal.diy on Your Website
- Part 3 — Building With @calcom/atoms (React SDK)
- Part 4 — The Platform API v2
- Common Tasks & Recipes
- Troubleshooting
- Frequently Asked Questions
- Reference
1. What Is Cal.diy?
Cal.diy is an open-source alternative to scheduling tools like Calendly. You create event types (for example, "30-minute intro call" or "60-minute consultation"), publish your booking page, and other people pick an open slot. Cal.diy checks your connected calendars for conflicts, reserves the slot, creates the meeting (including a video link when configured), and emails both parties.
It is built as a monorepo (one repository containing several independently shipped products) on top of:
- Next.js + React — the web application and UI
- tRPC — the internal type-safe API the web app calls
- Prisma + PostgreSQL — the data layer
- Tailwind CSS — styling
- Daily.co — default video conferencing ("Cal Video")
How Cal.diy differs from Cal.com
Cal.diy is a community fork with all "Enterprise Edition" code removed:
| Removed from Cal.diy | Still present |
|---|---|
| Teams, Organizations | Individual user scheduling |
| Insights / analytics dashboards | Event types & availability |
| Workflows (automated reminders) | Calendar & app integrations |
| SSO / SAML enterprise login | Email / password & Google login |
| License-key gating | Platform API v2 & embed SDKs |
Important
Cal.diy is 100% MIT-licensed with no "Open Core" split and no license key required — everything works out of the box. There is no hosted or managed version; you run it on your own infrastructure. Contributions to this repo do not flow back to Cal.com's production platform.
Note
Because this is a fork of a much larger commercial codebase, you will find scaffolding for team- and organization-related features in the source. This guide documents the individual-user scheduling flows, the embed SDKs, the React component library, and the Platform API — the surfaces that work without an enterprise plan.
2. The Four Products at a Glance
Cal.diy is not a single application — it ships four separately consumable products that share one scheduling backend. Pick the one that matches your goal.
| Product | Package | Who it's for | What you get |
|---|---|---|---|
| Scheduling web app | apps/web |
Self-hosters, individuals | A full booking platform you host: booking pages, event types, availability, integrations |
| Embed SDK | @calcom/embed-core, @calcom/embed-react, @calcom/embed-snippet |
Website owners | Drop your Cal.diy booking page into any site as an inline widget, popup, or floating button |
| Atoms (React SDK) | @calcom/atoms |
Product developers | Pre-built React components (<Booker>, <AvailabilitySettings>, …) to build scheduling into your own app |
| Platform API v2 | apps/api/v2 |
Backend developers | A versioned REST API for bookings, slots, event types, schedules, and calendars |
The rest of this guide has one dedicated part per product. Start with Part 1 if you want to run the booking platform; jump to Part 2/3/4 if you are a developer integrating scheduling into your own product.
┌─────────────────────────────┐
Your website ──embed─▶│ │
Your React app ─atoms─▶│ Cal.diy scheduling core │◀── booking page (apps/web)
Your backend ──API───▶│ (availability, bookings, │
│ calendars, conferencing) │◀── you (the host) manage here
└─────────────────────────────┘
3. Quick Start
The fastest way to see Cal.diy running locally is the yarn dx flow, which spins up a local PostgreSQL database (via Docker) seeded with test users.
Important
yarn dxrequires Docker and Docker Compose to be installed and running. It starts a throwaway Postgres instance and logs the test credentials in the console.
# 1. Clone the repository
git clone https://github.com/calcom/cal.diy.git
cd cal.diy
# 2. Install dependencies (Yarn 4 is bundled via .yarnrc.yml)
yarn
# 3. Create your environment file and generate secrets
cp .env.example .env
# Generate a cookie secret and an encryption key:
echo "NEXTAUTH_SECRET=$(openssl rand -base64 32)" >> .env
echo "CALENDSO_ENCRYPTION_KEY=$(openssl rand -base64 24)" >> .env
# 4. Start everything (DB + web app, with seeded users)
yarn dx
✔ Postgres started on localhost:5432
✔ Migrations applied
✔ Seed users created
▲ Next.js ready on http://localhost:3000
Open http://localhost:3000 and sign in with one of the seeded accounts:
| Password | Role | |
|---|---|---|
[email removed] |
free |
Free user |
[email removed] |
pro |
Pro user |
[email removed] |
trial |
Trial user |
[email removed] |
ADMINadmin2022! |
Admin user |
[email removed] |
onboarding |
Onboarding incomplete |
Tip
To browse the full seeded dataset, run
yarn db-studioand open http://localhost:5555 — this launches Prisma Studio, a visual database browser.
4. Installation
Cal.diy supports local development, Docker, and one-click cloud deployments. Choose the path that fits you.
Prerequisites
| Requirement | Version |
|---|---|
| Node.js | >= 18.x |
| PostgreSQL | >= 13.x |
| Yarn | recommended (bundled, Yarn 4) |
| Docker + Docker Compose | required only for yarn dx and the Docker deployment path |
Tip
The build is memory-hungry. Add
export NODE_OPTIONS="--max-old-space-size=16384"to your shell (adjust the number to the RAM you can spare) before running the app to avoid out-of-memory crashes during compilation.
Method 1 — Manual setup (local development)
macOS / Linux
git clone https://github.com/calcom/cal.diy.git
cd cal.diy
yarn
cp .env.example .env
# Edit .env: set DATABASE_URL, NEXTAUTH_SECRET, CALENDSO_ENCRYPTION_KEY
nvm install && nvm use # match the required Node version
yarn workspace @calcom/prisma db-migrate
yarn dev
Windows (Git Bash, admin)
git clone -c core.symlinks=true https://github.com/calcom/cal.diy.git
cd cal.diy
yarn
cp .env.example .env
# Replace the prisma .env symlink with a real copy (avoids a Prisma error):
rm packages/prisma/.env && cp .env packages/prisma/.env
yarn workspace @calcom/prisma db-migrate
yarn dev
Configure your database connection in .env:
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>/<db-name>'
# Example for a local Postgres:
# DATABASE_URL='postgresql://postgres:postgres@localhost:5432/calendso'
Note
Copy your
DATABASE_URLfrom.envinto.env.appStoreas well — the app-store build step reads it from there.
After the database is reachable, apply the schema:
# Development (creates/updates a dev database from schema.prisma):
yarn workspace @calcom/prisma db-migrate
# Production (applies committed migrations only):
yarn workspace @calcom/prisma db-deploy
Tip
To view emails Cal.diy sends during development, run MailHog:
docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhogThen open http://localhost:8025. This is required when
E2E_TEST_MAILHOG_ENABLED=1.
Method 2 — Docker Compose
The published image lives on Docker Hub at calcom/cal.diy.
git clone --recursive https://github.com/calcom/cal.diy.git
cd cal.diy
cp .env.example .env
# Generate the two required secrets BEFORE first start:
openssl rand -base64 32 # → put in NEXTAUTH_SECRET
openssl rand -base64 24 # → put in CALENDSO_ENCRYPTION_KEY
docker compose pull # optional: pre-pull images
docker compose up -d # full stack: Postgres + web app + Prisma Studio
You can also run subsets:
docker compose up -d calcom studio # app + Prisma Studio against a remote DB
docker compose up -d calcom # web app only (DATABASE_URL must point to a reachable DB)
Caution
Never ship the default
secretplaceholder values forNEXTAUTH_SECRETorCALENDSO_ENCRYPTION_KEYto production — they are a serious security risk.CALENDSO_ENCRYPTION_KEYmust be 32 bytes (AES-256) and must match between build time and run time.
Note
ARM users: pull the
-arm-suffixed image tag, e.g.docker pull calcom/cal.diy:v5.6.19-arm.
To update a running Docker stack:
docker compose down
docker compose pull
docker compose up -d
Method 3 — One-click cloud deployments
Cal.diy ships deploy buttons for several platforms (see the repository README for the live buttons):
| Platform | Notes |
|---|---|
| Railway | One-click template; PostgreSQL add-on included |
| Northflank | One-click stack deploy |
| Render | Deploy from the calcom/docker repo |
| Vercel | Requires the Pro plan (free plan limits serverless function count) |
| Heroku | app.json provisions heroku-postgresql:mini; runs prisma migrate deploy on deploy |
| Gitpod | Fully configured browser workspace, prebuilds enabled |
Warning
When deploying to Vercel, you must provide
DATABASE_URL,NEXT_PUBLIC_WEBAPP_URL,NEXTAUTH_URL,NEXTAUTH_SECRET,CRON_API_KEY, andCALENDSO_ENCRYPTION_KEYas environment variables.
5. First-Run Setup
The first time you open Cal.diy at your NEXT_PUBLIC_WEBAPP_URL (default http://localhost:3000), a setup wizard initializes. Define your first user and you're ready to go.
Creating your first user
If you didn't use the seeded yarn dx accounts, create a user one of two ways:
Approach 1 — Prisma Studio (manual):
yarn db-studio
- Open the
Usermodel and add a record. - Fill in
email,username, andpassword(encrypt the password with BCrypt). - Set
metadatato an empty object{}. - Save, then sign in at http://localhost:3000.
Approach 2 — Seed dummy users:
cd packages/prisma
yarn db-seed
Note
New users are placed on a
TRIALplan by default. You can change this default inpackages/prisma/schema.prisma.
Tip
If the setup wizard appears to require connecting a calendar but you'd rather skip it, navigate directly to
<NEXT_PUBLIC_WEBAPP_URL>/event-types. You can add calendar integrations later under Settings → Integrations.
6. Part 1 — Using the Scheduling Web App
This part covers the day-to-day of running your booking platform: creating event types, setting availability, managing bookings, and connecting integrations.
6.1 The booking lifecycle
You create an Event Type ─▶ You publish your Booking Page
│
A guest opens your page ◀──────────┘
│
Picks an open slot (the "Booker") ─▶ Slot is reserved
│
Fills in booking questions ─▶ Booking is confirmed
│
Calendar event + video link created ─▶ Confirmation emails sent
Note
Cal.diy uses a slot reservation system to prevent double-booking. When you open a booking page, available slots come from a
getSchedulequery that already excludes slots reserved by others. If two people open the same page and one selects a slot, the other will see a "Slot no longer available" state when they try to confirm. Availability is refreshed on window focus, on a configurable interval (default ~5 minutes), and when a slot is selected.
6.2 Event Types
An event type is a bookable offering. Create and manage them under Event Types in the dashboard. Each event type has its own slug and produces a booking URL like https://your-cal/<username>/<event-slug>.
Event types expose a rich set of options. The most important ones:
| Setting | What it does |
|---|---|
title / description |
Name and blurb shown on the booking page |
slug |
URL segment for the event (/you/30min) |
length |
Duration in minutes |
multipleDuration |
Offer several selectable durations |
locations |
Where the meeting happens (video app, phone, in person, custom) |
hidden |
Hide the event from your public profile |
requiresConfirmation |
You must approve each booking before it's confirmed |
requiresConfirmationThreshold |
Only require confirmation within X hours/minutes of the start |
requiresBookerEmailVerification |
Booker must verify their email before booking |
bookingRequiresAuthentication |
Booker must be signed in |
minimumBookingNotice |
Minimum lead time before a slot can be booked |
beforeEventBuffer / afterEventBuffer |
Padding before/after each meeting |
slotInterval |
Granularity of offered start times |
onlyShowFirstAvailableSlot |
Show just the next opening |
bookingLimits / durationLimits |
Cap bookings per day/week/month/year |
periodType / periodDays |
How far into the future people can book (rolling or fixed range) |
seatsPerTimeSlot |
Turn the event into a group/seated event |
seatsShowAttendees / seatsShowAvailabilityCount |
Reveal who's attending / seats left |
recurringEvent |
Make the event repeat (daily/weekly/monthly/yearly) |
price / currency |
Charge for the booking (requires a payment app) |
successRedirectUrl |
Send bookers to your own page after booking |
disableGuests |
Prevent bookers from adding extra guests |
disableCancelling / disableRescheduling |
Lock the booking after creation |
requiresCancellationReason |
Force a reason when cancelling |
hideCalendarNotes / hideCalendarEventDetails / hideOrganizerEmail |
Privacy controls on the calendar event |
lockTimeZoneToggleOnBookingPage / lockedTimeZone |
Pin the booking page to one time zone |
eventTypeColor |
Custom light/dark accent colors |
bookerLayouts |
Which calendar layouts the booker can use (see below) |
Tip
Booker layouts control how the date/time picker is presented. Three are available:
month_view(default),week_view, andcolumn_view. Set the enabled layouts and a default per event type, or globally in your profile.
Recurring events
Set recurringEvent to make an event repeat. The frequency uses standard recurrence rules:
| Frequency | Value |
|---|---|
| Yearly | YEARLY |
| Monthly | MONTHLY |
| Weekly | WEEKLY |
| Daily | DAILY |
You specify an interval (e.g. every 2 weeks) and a count (how many occurrences).
Booking questions (the form builder)
Each event type can collect custom information through booking questions. The form builder supports these field types:
| Type | Use |
|---|---|
name |
Booker name (first/last or single field) |
email |
Booker email |
phone |
Phone number |
text / textarea |
Short / long free text |
number |
Numeric input |
address |
Postal address |
multiemail |
Multiple email addresses |
select / multiselect |
Single / multiple choice from a list |
checkbox / radio |
Boolean groups / single choice |
radioInput |
A choice that reveals a follow-up input (e.g. pick "Phone" → show a phone field) |
boolean |
A single yes/no toggle |
url |
A URL field |
Fields can be marked required, hidden, given placeholders, and constrained with minLength/maxLength. You can also restrict bookers by email domain using require/exclude email rules (e.g. only allow @yourcompany.com).
Note
System fields (like name and email) cannot be deleted, but depending on their
editablesetting they can be hidden or made optional. Fully custom fields you add are completely editable.
6.3 Availability & Schedules
Under Availability, you define schedules — named sets of working hours and time zone (e.g. "Working Hours", "Weekend"). Assign a schedule to one or more event types via scheduleId.
Within a schedule you set:
- Weekly availability (which days and time ranges you're bookable)
- Date overrides (specific dates with different or no availability)
- The schedule's time zone
Tip
Cal.diy computes real availability by subtracting busy times (from connected calendars), existing bookings, buffers, and reserved slots from your schedule. The booking page and the actual booking call share the same availability logic so a slot shown as open will still be open when confirmed (barring a race with another booker).
6.4 The Booker (booking page)
Your public booking page is what guests use. It shows your event details, a calendar in the selected layout, open time slots, and the booking questions form. After a successful booking the guest sees a confirmation (or your successRedirectUrl).
The booking page URL pattern is:
https://<your-cal-domain>/<username>/<event-slug>
6.5 Managing bookings
Under Bookings you can see upcoming, past, cancelled, and unconfirmed bookings, and take action on each:
- Confirm / reject bookings that require confirmation
- Reschedule — move a booking to a new time (subject to
minimumRescheduleNoticeand reschedule locks) - Cancel — optionally requiring a cancellation reason; you can cancel a single booking, all remaining bookings in a series, or subsequent bookings
- Add/remove attendees and guests
- Mark no-show for host or attendees
- Reassign a booking to a different host (round-robin events)
Note
Every booking action — created, rescheduled, accepted, cancelled, rejected, attendees changed, no-show updates, seat changes — is recorded in an immutable booking audit trail. This is invisible to bookers but gives you a complete, tamper-proof history. See the Admin Guide for details.
Seated (group) events
Setting seatsPerTimeSlot turns an event into a group booking where multiple guests share one time slot. Each attendee gets a seat reference. You control whether attendees can see each other (seatsShowAttendees) and whether remaining seat counts are shown (seatsShowAvailabilityCount). When a seated slot is full you'll see "No more seats left at this seated booking."
6.6 Connecting calendars
Connect your calendars under Settings → Integrations (or Apps). Connected calendars are used for two things: checking your busy times (so you're never double-booked) and writing new bookings to a chosen destination calendar.
Supported calendar integrations include Google Calendar, Office 365 / Outlook, Apple Calendar (CalDAV), CalDAV servers, Exchange (2013/2016/EWS), Lark, Feishu, Zoho Calendar, and ICS feeds. Each requires the host operator to configure OAuth credentials (see your administrator or the Admin Guide).
Tip
You can connect multiple calendars for conflict-checking but pick a single destination calendar where new Cal.diy events are written.
6.7 Conferencing (video meetings)
By default, video meetings use Cal Video (powered by Daily.co). You can change the default conferencing app per user or per event type. Supported conferencing apps include Cal Video / Daily, Zoom, Google Meet, Microsoft Teams, Jitsi, Whereby, Around, Element Call, Huddle01, Mirotalk, Riverside, Salesroom, Sylaps, Tandem, Webex, and more.
Note
Setting an event's location to a video app means the meeting URL is generated automatically when the booking is confirmed and included in the calendar invite and emails.
6.8 Payments
Charge for bookings by setting an event type's price and currency and connecting a payment app. Supported payment integrations include Stripe, PayPal, HitPay, BTCPay Server, Alby (Lightning), and a built-in mock payment app for testing.
Note
When a paid event is booked, the booking enters an awaiting-payment state until the charge succeeds; an unpaid booking is excluded from your calendar until completed.
6.9 Other integrations & apps
Cal.diy ships a large app store of integrations beyond calendars, conferencing, and payments:
| Category | Examples |
|---|---|
| CRM | Salesforce, HubSpot, Pipedrive, Zoho CRM, Zoho Bigin, Close, Attio |
| Analytics | Google Analytics (GA4), Google Tag Manager, Plausible, Matomo, PostHog, Umami, Fathom, Meta Pixel, Databuddy |
| Automation | Zapier, Make, n8n, Pipedream |
| Messaging / reminders | WhatsApp (Twilio), Telegram, Signal, Discord, Basecamp, Intercom |
| Other | Giphy (thank-you pages), QR code, Weather in your calendar, Vital (health) |
Tip
Most third-party apps require credentials that the host operator configures via environment variables. See Integrations setup in the Admin Guide (Google, Microsoft, Zoom, Daily, Basecamp, HubSpot, Zoho, Pipedrive, etc.).
7. Part 2 — Embedding Cal.diy on Your Website
The embed SDK lets you place your Cal.diy booking flow on any website — as an inline widget, a popup triggered by a click, a floating button, or a modal. There are three packages:
| Package | Use |
|---|---|
@calcom/embed-snippet |
Vanilla JS loader snippet (no framework) |
@calcom/embed-core |
The embed engine (loaded by the snippet) |
@calcom/embed-react |
React wrapper: a <Cal> component + getCalApi() hook |
All embeds talk to a booking page identified by a cal link (e.g. pro for your-cal/pro) served from your Cal.diy instance's /embed/embed.js.
7.1 Vanilla JS
The snippet fetches embed.js and exposes a global Cal(...) function. Conceptually:
<!-- Load the embed snippet (points at YOUR Cal.diy instance) -->
<script>
(function (C, A, L) { /* … embed snippet … */ })(window, "https://your-cal/embed/embed.js", "init");
Cal("init", { origin: "https://your-cal" });
// Inline embed into a div
Cal("inline", {
elementOrSelector: "#my-cal-inline",
calLink: "pro",
config: { layout: "month_view", theme: "dark" },
});
// Customize the UI
Cal("ui", {
styles: { branding: { brandColor: "#000000" } },
hideEventTypeDetails: false,
});
</script>
<div id="my-cal-inline" style="width:100%;height:100%;overflow:scroll"></div>
You can also drive popups declaratively with data attributes on any element:
<button
data-cal-link="pro"
data-cal-namespace="my-popup"
data-cal-config='{"layout":"month_view","theme":"dark"}'>
Book a call
</button>
7.2 React (@calcom/embed-react)
Install the package and use the <Cal> component for inline embeds, or getCalApi() to drive popups, floating buttons, and events.
Inline embed:
import Cal, { getCalApi, type EmbedEvent } from "@calcom/embed-react";
import { useEffect } from "react";
export default function BookingWidget() {
useEffect(() => {
(async () => {
const cal = await getCalApi({ namespace: "inline" });
cal("ui", {
cssVarsPerTheme: {
light: { "cal-border-booker": "red" },
dark: { "cal-border-booker": "red" },
},
});
})();
}, []);
return (
<Cal
namespace="inline"
calOrigin="https://your-cal"
embedJsUrl="https://your-cal/embed/embed.js"
calLink="pro"
style={{ width: "100%", height: "100%", overflow: "scroll" }}
config={{
name: "John Doe",
email: "[email removed]",
notes: "Intro meeting",
guests: ["[email removed]"],
theme: "dark",
}}
/>
);
}
Floating button:
const cal = await getCalApi({ namespace: "floating", embedJsUrl: "https://your-cal/embed/embed.js" });
cal("floatingButton", {
calLink: "pro",
calOrigin: "https://your-cal",
config: { theme: "dark" },
});
Popup on element click:
const cal = await getCalApi({ namespace: "element-click" });
cal("ui", { styles: { branding: { brandColor: "#000000" } }, hideEventTypeDetails: false });
// then add data-cal-namespace="element-click" data-cal-link="pro" to a button
7.3 The embed API surface
The cal(...) function (returned by getCalApi(), or the global Cal) accepts these instructions:
| Instruction | Purpose |
|---|---|
"init" |
Initialize the embed and set the origin of your Cal.diy instance |
"inline" |
Render the booker inline into an element |
"floatingButton" |
Add a floating "Book" button |
"modal" / popup |
Open the booker in an overlay (also via data attributes) |
"ui" |
Style the booker (brand color, CSS variables per theme, hide event details) |
"preload" |
Pre-fetch a booking page for instant opening |
"on" / "off" |
Subscribe / unsubscribe to embed events |
Embed config options (passed via config or data-cal-config):
| Option | Example |
|---|---|
layout |
month_view, week_view, column_view |
theme |
dark, light |
name, email, notes, guests |
Prefill the booking form |
useSlotsViewOnSmallScreen |
"true" to force the slots list on mobile |
7.4 Embed events
Subscribe to lifecycle events to react in your own app (analytics, redirects, etc.):
const cal = await getCalApi({ namespace: "inline" });
cal("on", { action: "*", callback: (e) => console.log(e.detail) });
cal("on", {
action: "bookingSuccessfulV2",
callback: (e: EmbedEvent<"bookingSuccessfulV2">) => {
const { startTime, endTime, title } = e.detail.data;
console.log("Booked:", title, startTime, endTime);
},
});
| Event | Fires when |
|---|---|
linkReady |
The embed iframe has loaded the booking page |
bookerReady |
The booker UI is interactive (gives eventId, eventSlug) |
bookingSuccessful / bookingSuccessfulV2 |
A booking completes (V2 includes startTime, endTime, title) |
* |
Wildcard — receive all events |
Tip
Always pair an
cal("on", …)with a matchingcal("off", …)(for example in a ReactuseEffectcleanup) to avoid duplicate handlers across re-renders.
8. Part 3 — Building With @calcom/atoms (React SDK)
@calcom/atoms is a library of customizable React UI components that let you build a full scheduling experience inside your own product — without iframes. Where the embed SDK drops in a hosted booking page, atoms give you native components you compose and style.
Note
Atoms are designed for the Platform model: your app authenticates as a Cal.diy OAuth client and manages "managed users" through the Platform API v2. Atoms talk to the API at
NEXT_PUBLIC_CALCOM_API_URL(e.g.http://localhost:5555/api/v2). See Part 4 for the API and the Admin Guide for OAuth-client setup.
8.1 Provider
Wrap your app in a provider. Use CalProvider for a platform OAuth client, or CalOAuthProvider for an OAuth 2.0 client flow:
import { CalProvider } from "@calcom/atoms";
<CalProvider
clientId={process.env.NEXT_PUBLIC_X_CAL_ID}
options={{ apiUrl: "http://localhost:5555/api/v2" }}
accessToken={managedUserAccessToken}
>
{/* your scheduling UI */}
</CalProvider>
8.2 Components
The package exports ready-made components for every part of scheduling:
| Component | What it renders |
|---|---|
<Booker> |
The full booking flow (pick a slot, fill the form, book) |
<BookerEmbed> |
Booker pre-configured for embedding |
<AvailabilitySettings> |
Edit a schedule's working hours |
<CreateSchedule> / <ListSchedules> |
Create / list availability schedules |
<ListEventTypes> / <CreateEventType> / <EventTypeSettings> |
Manage event types |
<CalendarSettings> / <CalendarView> |
Calendar configuration and views |
<SelectedCalendarsSettings> / <DestinationCalendarSettings> |
Choose conflict-check and destination calendars |
<ConferencingAppsSettings> |
Manage video apps |
<Connect.*>, <GcalConnect>, <OutlookConnect>, <StripeConnect> |
Connect Google / Outlook / Stripe |
<TroubleShooter> |
Diagnose why slots aren't showing |
8.3 Hooks
Atoms also expose hooks for data access and mutations:
| Hook | Returns / does |
|---|---|
useMe() |
The current managed user |
useIsPlatform() |
Whether you're running in platform mode |
useAvailableSlots() |
Open slots for an event type |
useBooking() / useBookings() |
A single / list of bookings |
useCancelBooking() |
Cancel a booking |
useEventTypes() / useEventType() (useEvent) |
List / get event types |
useCreateEventType() / useEventTypeById() |
Create / fetch an event type |
useConnectedCalendars() / useAddSelectedCalendar() / useRemoveSelectedCalendar() |
Manage connected calendars |
useDeleteCalendarCredentials() |
Disconnect a calendar |
useAtomsContext() |
Access provider context |
Important
Atoms ship their own CSS scoped under
.calcom-atoms(so they don't collide with your app's styles). Import the package's stylesheet and ensure your build externalizesreact/react-dom. ThewebAppUrlglobal config (defaults tohttps://cal.com) tells the Booker which backend to fetch from.
9. Part 4 — The Platform API v2
The Platform API (apps/api/v2) is a versioned REST API built on NestJS for programmatically managing bookings, slots, event types, schedules, and calendars. It is the backend that powers the atoms SDK and any custom integration you build.
9.1 Base URL and health
By default the API runs on port 5555 and is served under the /v2 prefix (with /api/v2 rewritten to /v2):
# Health check (no auth):
curl http://localhost:5555/health
# → OK
9.2 Authentication
The API accepts three authentication modes:
| Mode | How | Use case |
|---|---|---|
| API key | Authorization: Bearer cal_xxx |
Server-to-server, single account |
| OAuth client | Headers x-cal-client-id + x-cal-secret-key |
Platform apps managing many users |
| OAuth 2.0 access token | Authorization: Bearer <access_token> |
Delegated user access via the authorize/token flow |
# Fetch the authenticated user with an API key:
curl http://localhost:5555/v2/me \
-H "Authorization: Bearer cal_live_xxxxxxxx"
{ "status": "success", "data": { "id": 1, "email": "[email removed]", "username": "you" } }
9.3 Versioning
The API uses custom header-based versioning. Send the version with the cal-api-version header; if omitted, it defaults to 2024-04-15.
curl http://localhost:5555/v2/bookings \
-H "Authorization: Bearer cal_live_xxx" \
-H "cal-api-version: 2024-08-13"
Different resources have their own dated versions, for example:
| Resource | Versions |
|---|---|
| Bookings | 2024-04-15, 2024-08-13 |
| Event types | 2024-04-15, 2024-06-14 |
| Schedules | 2024-04-15, 2024-06-11 |
| Slots | 2024-04-15, 2024-09-04 |
9.4 Endpoint groups
The API surface is organized into modules. The most useful for building scheduling:
| Group | What it does |
|---|---|
/v2/me |
The authenticated user |
/v2/bookings |
Create, get, list, reschedule, cancel, mark no-show, reassign, manage attendees/guests/seats |
/v2/slots |
Query available slots; reserve and release slots |
/v2/event-types |
Manage event types |
/v2/schedules |
Manage availability schedules |
/v2/calendars |
Connected calendars, busy times |
/v2/selected-calendars / /v2/destination-calendars |
Conflict-check and write targets |
/v2/conferencing |
Video app connections |
/v2/webhooks |
Register webhooks for booking events |
/v2/oauth-clients / /v2/auth/oauth2/* |
Platform OAuth clients and the OAuth 2.0 flow |
/v2/api-keys |
Manage API keys |
/v2/timezones |
Time zone helpers |
Tip
In development the API generates an OpenAPI/Swagger document at startup, so you can explore the full, exact request/response schemas interactively.
9.5 Rate limits
Every authenticated response carries rate-limit headers, and exceeding a limit returns HTTP 429:
x-ratelimit-limit-default: 120
x-ratelimit-remaining-default: 118
x-ratelimit-reset-default: 42
Limits can be the default or custom per API key, and multiple windows (e.g. a short and a long limit) can apply simultaneously. When blocked, wait for the x-ratelimit-reset-* window (seconds) before retrying.
10. Common Tasks & Recipes
Reset your local database and reseed
cd packages/prisma
yarn db-seed # populate with dummy users
# or, for a full reset (development only):
yarn workspace @calcom/prisma db-reset
Control log verbosity
Set NEXT_PUBLIC_LOGGER_LEVEL in .env to control how much Cal.diy logs (for all tRPC queries/mutations):
echo 'NEXT_PUBLIC_LOGGER_LEVEL=3' >> .env # info and above
| Level | Meaning |
|---|---|
0 |
silly |
The logger includes the chosen level and higher.
Upgrade to a new version
git pull
yarn # pick up dependency changes
yarn workspace @calcom/prisma db-deploy # apply migrations (production)
yarn predev # check for .env changes
yarn build && yarn start # production build
Caution
In a development environment,
db-migratecan reset your local database. Usedb-deployin production.
Add Google Calendar to your App Store
After your administrator configures GOOGLE_API_CREDENTIALS, repopulate the app store:
cd packages/prisma
yarn seed-app-store
Enable rate limiting (optional, via Unkey)
Cal.diy can use Unkey for rate limiting. Create a root key with ratelimit.create_namespace and ratelimit.limit permissions and set UNKEY_ROOT_KEY in .env. Without it, Cal.diy runs normally without rate limiting.
11. Troubleshooting
Error: No key set vapidDetails.publicKey
Cause: Web Push (VAPID) keys are missing.
Solution: Generate them and add to .env:
npx web-push generate-vapid-keys
NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key_here
VAPID_PRIVATE_KEY=your_private_key_here
Prevention: Set these before first run; never commit real keys to .env.example.
Error: [next-auth][error][CLIENT_FETCH_ERROR]
Cause: In Docker, the container can't resolve its own WEBAPP_URL for the auth callback (it doesn't share your machine's DNS).
Solution: Set NEXTAUTH_URL so the backend loops back to itself:
NEXTAUTH_URL=http://localhost:3000/api/auth
Error: Invalid 'prisma.user.create()' when creating a user
Cause: The metadata field is empty.
Solution: Use an empty JSON object {} for metadata, and leave id empty so it autoincrements.
Requests rejected behind a load balancer (SSL edge termination)
Cause: Your load balancer terminates SSL and the app rejects the upstream certificate.
Solution (only if you trust the LB): Set NODE_TLS_REJECT_UNAUTHORIZED=0.
E2E test browsers not installed
Error: Executable doesn't exist at .../ms-playwright/chromium-.../Chromium
Solution: npx playwright install
Prisma error on Windows: unexpected character / in variable name
Cause: The packages/prisma/.env symlink isn't followed on Windows.
Solution: Replace the symlink with a real copy:
rm packages/prisma/.env && cp .env packages/prisma/.env
"Slot no longer available" when confirming a booking
Cause: Another booker reserved or booked that slot between when you loaded the page and when you clicked confirm. Solution: Pick a different slot. The page re-fetches availability on focus and on a timer; refresh to see the latest openings.
API returns 429 Too Many Requests
Cause: You exceeded a rate-limit window.
Solution: Read the x-ratelimit-reset-* header (seconds until reset) and back off until then. Consider requesting a higher custom limit on your API key.
Booking API errors (Platform API v2)
Common, exactly-worded responses you may hit:
| Message | Meaning |
|---|---|
Event Type not found / Event Type with ID=… not found |
The eventTypeId is wrong or hidden |
User with username … not found |
Bad username in the request |
Invalid start date / Invalid end date |
Malformed or out-of-range date |
Attempting to book a meeting in the past. |
Start time is before now |
User either already has booking at this time or is not available |
Slot conflicts with an existing booking/busy time |
No more seats left at this seated booking. |
Seated event is full |
Invalid Access Token, not present in Redis or DB |
Expired/invalid OAuth access token |
12. Frequently Asked Questions
Q: Is Cal.diy the same as Cal.com? No. Cal.diy is the community fork of Cal.com with all enterprise/commercial features removed (Teams, Organizations, Insights, Workflows, SSO/SAML) and no license key. It's 100% MIT-licensed and self-hosted only.
Q: Is there a hosted version of Cal.diy I can sign up for? No. Cal.diy has no managed/hosted offering — you run it on your own infrastructure. For a hosted, commercial product use Cal.com.
Q: Do I need a license key? No. Everything works out of the box without any Cal.com account or license.
Q: What database does it use?
PostgreSQL (>= 13). The schema is managed by Prisma in packages/prisma/schema.prisma.
Q: How do I get video meetings working?
Cal Video (Daily.co) is the default. Set DAILY_API_KEY (your administrator does this) or connect another video app like Zoom or Google Meet per event type.
Q: Can I charge for bookings?
Yes. Set an event type's price and currency and connect a payment app (Stripe, PayPal, HitPay, BTCPay, Alby).
Q: Can I embed the booking page in my site without React?
Yes. Use @calcom/embed-snippet (vanilla JS) with data-cal-link attributes or the global Cal(...) function.
Q: What's the difference between the embed SDK and atoms? The embed SDK drops a hosted booking page into your site via an iframe. Atoms are native React components you compose and style directly, backed by the Platform API.
Q: How do I prefill the booking form?
Pass name, email, notes, and guests in the embed config (or as data-cal-config), or as query params on the booking URL.
Q: How far in advance can people book?
Controlled per event type via periodType, periodDays, or a fixed periodStartDate/periodEndDate range, plus minimumBookingNotice for the near end.
Q: Why is a slot I expect to be open not showing?
It may conflict with a connected calendar's busy time, an existing booking, a buffer (beforeEventBuffer/afterEventBuffer), minimumBookingNotice, a booking limit, or a slot reserved by another booker. Use the <TroubleShooter> atom or check your schedule.
Q: Can I restrict who can book by email? Yes — booking-question fields support require/exclude email-domain rules, and you can require email verification or authentication on an event type.
Q: How do I run it on ARM (Apple Silicon, ARM servers)?
Pull the -arm Docker image tag, e.g. calcom/cal.diy:v5.6.19-arm.
Q: Where do bookings get written? To the destination calendar you select. Other connected calendars are used only for conflict checking.
Q: How do I see emails during development?
Run MailHog (docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog) and open http://localhost:8025.
Q: Is there an API to build my own scheduling UI?
Yes — the Platform API v2 (REST, NestJS) plus the @calcom/atoms React components. See Parts 3 and 4.
13. Reference
Key commands
| Command | What it does |
|---|---|
yarn |
Install dependencies (Yarn 4) |
yarn dx |
Start DB + app with seeded users (needs Docker) |
yarn dev |
Start the dev server |
yarn build / yarn start |
Production build / start |
yarn db-studio |
Open Prisma Studio (DB browser) |
yarn workspace @calcom/prisma db-migrate |
Apply schema (development) |
yarn workspace @calcom/prisma db-deploy |
Apply migrations (production) |
cd packages/prisma && yarn db-seed |
Seed dummy users |
cd packages/prisma && yarn seed-app-store |
Repopulate the app store |
yarn test-e2e |
Run Playwright end-to-end tests |
Default ports
| Port | Service |
|---|---|
3000 |
Web app |
5555 |
Prisma Studio / Platform API |
1025 / 8025 |
MailHog SMTP / web UI |
Essential environment variables (user-facing)
| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
NEXT_PUBLIC_WEBAPP_URL |
Public base URL of your instance |
NEXTAUTH_URL |
Auth server URL (defaults to {WEBAPP_URL}/api/auth) |
NEXTAUTH_SECRET |
Cookie encryption key (openssl rand -base64 32) |
CALENDSO_ENCRYPTION_KEY |
App encryption key, 32 bytes (openssl rand -base64 24) |
NEXT_PUBLIC_VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY |
Web Push keys |
NEXT_PUBLIC_LOGGER_LEVEL |
Log verbosity (0–6) |
The complete, grouped environment-variable reference (integrations, Stripe, email, video, rate limits, the Platform API) is in the Admin Guide.
Booker layouts
| Layout | Value |
|---|---|
| Month view (default) | month_view |
| Week view | week_view |
| Column view | column_view |
Where to get help
- Discussions: github.com/calcom/cal.diy/discussions
- Issues: github.com/calcom/cal.diy/issues
- Contributing: see
CONTRIBUTING.mdin the repository