# Cal.diy User Guide

Cal.diy is the community-driven, fully open-source scheduling platform — a fork of [Cal.com](https://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](https://cal.com/sales) instead.

---

## Table of Contents

1. [What Is Cal.diy?](#1-what-is-caldiy)
2. [The Four Products at a Glance](#2-the-four-products-at-a-glance)
3. [Quick Start](#3-quick-start)
4. [Installation](#4-installation)
5. [First-Run Setup](#5-first-run-setup)
6. [Part 1 — Using the Scheduling Web App](#6-part-1--using-the-scheduling-web-app)
7. [Part 2 — Embedding Cal.diy on Your Website](#7-part-2--embedding-caldiy-on-your-website)
8. [Part 3 — Building With @calcom/atoms (React SDK)](#8-part-3--building-with-calcomatoms-react-sdk)
9. [Part 4 — The Platform API v2](#9-part-4--the-platform-api-v2)
10. [Common Tasks & Recipes](#10-common-tasks--recipes)
11. [Troubleshooting](#11-troubleshooting)
12. [Frequently Asked Questions](#12-frequently-asked-questions)
13. [Reference](#13-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](https://nextjs.org/)** + **[React](https://reactjs.org/)** — the web application and UI
- **[tRPC](https://trpc.io/)** — the internal type-safe API the web app calls
- **[Prisma](https://prisma.io/)** + **PostgreSQL** — the data layer
- **[Tailwind CSS](https://tailwindcss.com/)** — styling
- **[Daily.co](https://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.

```text
                         ┌─────────────────────────────┐
   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 dx` **requires Docker and Docker Compose** to be installed and running. It starts a throwaway Postgres instance and logs the test credentials in the console.

```bash
# 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
```

```text
✔ Postgres started on localhost:5432
✔ Migrations applied
✔ Seed users created
▲ Next.js ready on http://localhost:3000
```

Open [http://localhost:3000](http://localhost:3000) and sign in with one of the seeded accounts:

| Email | 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-studio` and open [http://localhost:5555](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"

    ```bash
    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)"

    ```bash
    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`:

```bash
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_URL` from `.env` into `.env.appStore` as well — the app-store build step reads it from there.

After the database is reachable, apply the schema:

```bash
# 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](https://github.com/mailhog/MailHog):
> ```bash
> docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog
> ```
> Then open [http://localhost:8025](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`](https://hub.docker.com/r/calcom/cal.diy).

```bash
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:

```bash
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 `secret` placeholder values for `NEXTAUTH_SECRET` or `CALENDSO_ENCRYPTION_KEY` to production — they are a serious security risk. `CALENDSO_ENCRYPTION_KEY` must 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:

```bash
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`, and `CALENDSO_ENCRYPTION_KEY` as 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):**

```bash
yarn db-studio
```

1. Open the `User` model and add a record.
2. Fill in `email`, `username`, and `password` (encrypt the password with [BCrypt](https://bcrypt-generator.com/)).
3. Set `metadata` to an empty object `{}`.
4. Save, then sign in at [http://localhost:3000](http://localhost:3000).

**Approach 2 — Seed dummy users:**

```bash
cd packages/prisma
yarn db-seed
```

> [!NOTE]
> New users are placed on a `TRIAL` plan by default. You can change this default in `packages/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

```text
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 `getSchedule` query 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`, and `column_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 `editable` setting 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:

```text
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 `minimumRescheduleNotice` and 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:

```html
<!-- 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:

```html
<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:**

```tsx
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:**

```tsx
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:**

```tsx
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.):

```tsx
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 matching `cal("off", …)` (for example in a React `useEffect` cleanup) 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:

```tsx
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 externalizes `react` / `react-dom`. The `webAppUrl` global config (defaults to `https://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`):

```bash
# 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 |

```bash
# Fetch the authenticated user with an API key:
curl http://localhost:5555/v2/me \
  -H "Authorization: Bearer cal_live_xxxxxxxx"
```

```json
{ "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`.

```bash
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**:

```text
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

```bash
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):

```bash
echo 'NEXT_PUBLIC_LOGGER_LEVEL=3' >> .env   # info and above
```

| Level | Meaning |
| --- | --- |
| `0` | silly | `1` trace | `2` debug | `3` info | `4` warn | `5` error | `6` fatal |

The logger includes the chosen level **and higher**.

### Upgrade to a new version

```bash
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-migrate` can reset your local database. Use `db-deploy` in production.

### Add Google Calendar to your App Store

After your administrator configures `GOOGLE_API_CREDENTIALS`, repopulate the app store:

```bash
cd packages/prisma
yarn seed-app-store
```

### Enable rate limiting (optional, via Unkey)

Cal.diy can use [Unkey](https://unkey.com) 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`:

```bash
npx web-push generate-vapid-keys
```

```env
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:

```env
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:

```bash
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](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](https://github.com/calcom/cal.diy/discussions)
- **Issues:** [github.com/calcom/cal.diy/issues](https://github.com/calcom/cal.diy/issues)
- **Contributing:** see `CONTRIBUTING.md` in the repository

---

_Generated June 2026 from calcom/cal.diy at d1ad4ea. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
