# Cal.diy Admin Guide

This guide is the operational and architectural reference for **Cal.diy** — the community-driven, fully open-source (MIT) fork of Cal.com with all enterprise/commercial code removed. It is written for the people who run, deploy, secure, and extend a Cal.diy instance: administrators, DevOps engineers, and developers integrating against the Platform API.

Where the **User Guide** shows what you can *do* with Cal.diy, this guide explains *how the system is built and operated*: the monorepo architecture, deployment topologies, the complete configuration surface, the data model, the Platform API internals, security posture, and day-2 operations.

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [Architecture Overview](#2-architecture-overview)
3. [Technology Stack](#3-technology-stack)
4. [System Requirements](#4-system-requirements)
5. [Installation & Deployment](#5-installation--deployment)
6. [Configuration Reference](#6-configuration-reference)
7. [Database & Data Model](#7-database--data-model)
8. [The Platform API v2 (Internals)](#8-the-platform-api-v2-internals)
9. [Integrations & the App Store](#9-integrations--the-app-store)
10. [Calendar Cache & Sync](#10-calendar-cache--sync)
11. [Security](#11-security)
12. [Operations](#12-operations)
13. [Performance & Scaling](#13-performance--scaling)
14. [Internationalization](#14-internationalization)
15. [Testing Strategy](#15-testing-strategy)
16. [Repository Structure (for Developers)](#16-repository-structure-for-developers)
17. [Operational Troubleshooting](#17-operational-troubleshooting)
18. [Potential Improvements & Tech Debt](#18-potential-improvements--tech-debt)
19. [Quick Reference](#19-quick-reference)

---

## 1. Executive Summary

Cal.diy is a **monorepo** that ships four products from one scheduling backend:

- A self-hostable **Next.js scheduling web application** (`apps/web`) — the flagship.
- A **NestJS Platform API v2** (`apps/api/v2`) — a versioned REST API for programmatic scheduling.
- A **React component SDK** (`@calcom/atoms`) — native scheduling components for your own app.
- An **embed SDK** (`@calcom/embed-core`/`embed-react`/`embed-snippet`) — drop the booking page into any website.

It is built on **Next.js + React + tRPC + Prisma/PostgreSQL + Redis**, styled with **Tailwind CSS**, linted/formatted with **Biome**, and tested with **Vitest** + **Playwright**. The build orchestrator is **Turborepo** over **Yarn 4 workspaces**.

| Property | Value |
| --- | --- |
| License | MIT (no Open Core, no license key) |
| Primary language | TypeScript |
| Package manager | Yarn 4 (`nodeLinker: node-modules`) |
| Build system | Turborepo |
| Database | PostgreSQL ≥ 13 (Prisma ORM) |
| Cache / queue / rate limiting | Redis (BullMQ, throttler storage) |
| Default video | Cal Video (Daily.co) |
| Deployment targets | Docker, Vercel, Railway, Northflank, Render, Heroku |
| Error monitoring | Sentry |
| Background jobs | Tasker + trigger.dev |

> [!IMPORTANT]
> Cal.diy is a fork with enterprise features (Teams, Organizations, Insights, Workflows, SSO/SAML) removed. The codebase still contains scaffolding (e.g. `Team`/`Membership`/`OAuthClient` tables, organization references in the Platform API), but the supported, license-free surface is individual scheduling, the embed SDKs, atoms, and the Platform API.

---

## 2. Architecture Overview

### 2.1 Monorepo layout

```text
cal.diy/
├── apps/
│   ├── web/                  Next.js scheduling web app (flagship, port 3000)
│   ├── api/v2/               NestJS Platform API (port 5555, /v2 prefix)
│   └── docs/                 Nextra docs site (@calcom/docs)
├── packages/
│   ├── prisma/               Schema, migrations, seed, generated client
│   ├── trpc/                 Internal type-safe API (web app ↔ server)
│   ├── features/             Domain logic: bookings, availability, event-types,
│   │                         auth, webhooks, schedules, calendars, etc.
│   ├── app-store/            100+ integrations (calendar, video, payment, CRM…)
│   ├── platform/
│   │   ├── atoms/            @calcom/atoms — React component SDK
│   │   ├── types/            @calcom/platform-types (API DTOs)
│   │   ├── constants/        @calcom/platform-constants
│   │   ├── enums/            @calcom/platform-enums
│   │   ├── libraries/        @calcom/platform-libraries
│   │   └── examples/         Example apps consuming atoms
│   ├── embeds/
│   │   ├── embed-core/       Embed engine (vanilla)
│   │   ├── embed-react/      React <Cal> + getCalApi()
│   │   └── embed-snippet/    Loader snippet
│   ├── lib/                  Shared utilities (largest package, no upward deps)
│   ├── ui/                   Design system components
│   ├── emails/               Email templates (React Email)
│   ├── i18n/                 Locale JSON (38 target languages)
│   └── kysely/, dayjs/, sms/, coss-ui/, …  supporting packages
└── turbo.json, biome.json, playwright.config.ts, vitest.workspace.ts
```

### 2.2 Module boundary enforcement

Cal.diy enforces a strict dependency hierarchy through Biome `noRestrictedImports` rules to prevent circular dependencies. The key constraints:

```text
apps/web ──▶ trpc ──▶ features ──▶ app-store ──▶ lib
                                            └────▶ prisma

Rules (enforced at lint time):
  • lib       MUST NOT import from app-store, features, trpc, @trpc/server
  • app-store MUST NOT import from features, trpc
  • features  MUST NOT import from trpc, apps/web
  • trpc      MUST NOT import from apps/web
  • atoms     MUST NOT import from trpc (it uses the Platform API instead)
  • api/v2    MAY import ONLY from @calcom/platform-* and @calcom/prisma
```

> [!NOTE]
> The `lib` package is the dependency floor (152 files, ~410 KB) and may only depend on `prisma`. This makes it the safe place for shared logic. The API v2 is deliberately isolated — it can only touch the `platform-*` packages and `prisma`, never the web app's `features`/`trpc` code.

### 2.3 Request flow — web app

```text
Browser
  │  HTTP
  ▼
Next.js (apps/web)  ── SSR pages + App Router ──┐
  │  tRPC call                                   │ NextAuth (cookies)
  ▼                                              ▼
packages/trpc (server routers)            packages/features/auth
  │                                              │
  ▼                                              ▼
packages/features (booking, availability)   Prisma  ◀── PostgreSQL
  │                                              ▲
  ▼                                              │
packages/app-store (calendar/video/payment) ────┘  external APIs (Google, Stripe…)
```

### 2.4 Request flow — Platform API

```text
Client (API key / OAuth client / OAuth2 token)
  │  HTTPS, header: cal-api-version
  ▼
NestJS (apps/api/v2)
  ├─ Middleware: RawBody/Json/Urlencoded body, RequestId, Logger, Redirects, Rewriter
  ├─ Guards: CustomThrottlerGuard (Redis rate limit), Auth guards
  ├─ ValidationPipe (whitelist + transform, class-validator DTOs)
  ├─ Versioning: custom header extractor (default 2024-04-15)
  ├─ Controllers → Services → Repositories → Prisma → PostgreSQL
  ├─ Queue: BullMQ on Redis
  └─ Filters: Prisma / Zod / Http / TRPC / CalendarService exceptions → Sentry
```

> [!TIP]
> The API can run **two ways from one codebase**: as a long-lived local server (`main.ts` `run()`, listens on `API_PORT`) or as a **Vercel serverless function** (a cached singleton `NestServer` Express instance). The same `bootstrap()` configures pipes, filters, CORS, and helmet in both modes.

---

## 3. Technology Stack

| Layer | Technology | Notes |
| --- | --- | --- |
| Language | TypeScript | Strict in most packages |
| Web framework | Next.js 15/16, React 19 | App Router + legacy pages |
| Internal API | tRPC | Type-safe web ↔ server |
| Platform API | NestJS 10 | REST, header versioning, Swagger |
| ORM | Prisma 6.16 | Custom generated client + extensions |
| Database | PostgreSQL ≥ 13 (Postgres 18 in test compose) | Read/write URL split supported |
| DB adapter | `@prisma/adapter-pg` + `pg` Pool | Optional connection pooling |
| Cache/queue | Redis | BullMQ jobs, throttler storage |
| Styling | Tailwind CSS 4 | Scoped for atoms via `postcss-prefixwrap` |
| Lint/format | Biome 2.3 | Replaces ESLint + Prettier |
| Unit tests | Vitest 4 | Multi-project workspace |
| E2E tests | Playwright | Web + embed projects, multi-browser |
| Perf tests | Grafana k6 | Booking flow load/stress/spike |
| Background jobs | Tasker + trigger.dev | Async tasks, cron |
| Monitoring | Sentry | Errors + profiling |
| Synthetic checks | Checkly | `__checks__/**/*.check.ts` |
| Validation | Zod + class-validator | Web (Zod) / API (class-validator) |
| Date/time | Day.js + Luxon | |
| i18n | i18next + Lingo.dev | 38 locales |
| Email | React Email + SMTP/SendGrid/Resend | MailHog in dev |

---

## 4. System Requirements

| Component | Minimum | Notes |
| --- | --- | --- |
| Node.js | ≥ 18.x | `nvm use` respects the pinned version |
| PostgreSQL | ≥ 13.x | Postgres 18 used in the integration-test compose |
| Yarn | 4.12.0 (bundled) | `.yarnrc.yml`, `nodeLinker: node-modules` |
| Redis | required for the Platform API | rate limiting, queue, throttler storage |
| RAM (build) | 4 GB min (`MAX_OLD_SPACE_SIZE=4096`) | 16 GB recommended for `NODE_OPTIONS=--max-old-space-size=16384` |
| Docker + Compose | for `yarn dx` / Docker deploy | |

> [!CAUTION]
> The Docker build currently **requires a reachable database at build time** (application configuration is resolved during build). Either point `DATABASE_URL` at an existing DB or start a temporary one (`docker compose up -d database`). Also note `DOCKER_BUILDKIT=0` is required when building from source to allow a network bridge.

---

## 5. Installation & Deployment

### 5.1 Local development

```bash
git clone https://github.com/calcom/cal.diy.git
cd cal.diy
yarn
cp .env.example .env
echo "NEXTAUTH_SECRET=$(openssl rand -base64 32)" >> .env
echo "CALENDSO_ENCRYPTION_KEY=$(openssl rand -base64 24)" >> .env
# Set DATABASE_URL, then:
yarn workspace @calcom/prisma db-migrate
yarn dev
```

`yarn dx` automates the above with a Dockerized Postgres and seeded users (see the User Guide). The Turbo `dx`/`dev` tasks depend on `env-check:common` and `env-check:app-store`, which compare your `.env`/`.env.appStore` against the example files.

### 5.2 Docker Compose (production)

```bash
git clone --recursive https://github.com/calcom/cal.diy.git
cd cal.diy
cp .env.example .env
# Generate and set NEXTAUTH_SECRET (rand 32) and CALENDSO_ENCRYPTION_KEY (rand 24)
docker compose up -d            # full stack (db + calcom + studio)
docker compose up -d calcom     # web app only, against external DATABASE_URL
```

Build from source:

```bash
docker compose up -d database                       # temp DB for build
DOCKER_BUILDKIT=0 docker compose build calcom
docker compose up -d
```

> [!IMPORTANT]
> `NEXTAUTH_SECRET` and `CALENDSO_ENCRYPTION_KEY` must be **identical at build time and run time**. If `NEXT_PUBLIC_WEBAPP_URL` differs between build and run, the container performs a brief static-file rewrite on start.

### 5.3 Build-time vs run-time variables

| Variable | Build-time | Run-time | Default |
| --- | --- | --- | --- |
| `DATABASE_URL` | ✅ required | ✅ required | `postgresql://unicorn_user:magical_password@database:5432/calendso` |
| `NEXTAUTH_SECRET` | ✅ required | ✅ required | `secret` (change it!) |
| `CALENDSO_ENCRYPTION_KEY` | ✅ required | ✅ required | `secret` (32 bytes) |
| `MAX_OLD_SPACE_SIZE` | ✅ required | — | `4096` |
| `NEXT_PUBLIC_WEBAPP_URL` | optional | optional | `http://localhost:3000` |
| `NEXTAUTH_URL` | — | optional | `{WEBAPP_URL}/api/auth` |
| `CALCOM_TELEMETRY_DISABLED` | optional | optional | (set `1` to disable telemetry) |

### 5.4 Cloud platforms

| Platform | Mechanism |
| --- | --- |
| **Vercel** | Pro plan required (serverless function count). API v2 runs as a serverless handler. `postdeploy` runs `prisma migrate deploy`. |
| **Railway** | One-click template + Postgres add-on |
| **Northflank** | One-click stack |
| **Render** | Deploy from `calcom/docker` |
| **Heroku** | `app.json` provisions `heroku-postgresql:mini`, `postdeploy` migrates |
| **Gitpod** | `.gitpod.yml` runs `yarn`, generates secrets, `yarn dx`; ports 3000/5420/1025/8025 |

### 5.5 Deploy flow & migrations

```bash
git pull
yarn
yarn workspace @calcom/prisma db-deploy   # production: apply committed migrations
yarn predev                               # detect .env changes
yarn build && yarn start
```

> [!WARNING]
> Use `db-deploy` (applies only committed migrations) in production. `db-migrate` is for development and can reset the database. Set `SKIP_DB_MIGRATIONS=1` to skip the auto-migration step (`auto-migrations.ts`) — it also skips automatically if `DATABASE_URL`/`DATABASE_DIRECT_URL` are unset or Prisma can't connect.

---

## 6. Configuration Reference

Cal.diy is configured almost entirely through environment variables. The authoritative list is `turbo.json`'s `globalEnv`. They are grouped below.

### 6.1 Core (required)

| Variable | Purpose |
| --- | --- |
| `DATABASE_URL` | PostgreSQL connection (point at a pooler if used) |
| `DATABASE_DIRECT_URL` | Direct DB URL (for migrations when pooling) |
| `NEXT_PUBLIC_WEBAPP_URL` | Public base URL of the instance |
| `NEXTAUTH_URL` | Auth server URL |
| `NEXTAUTH_SECRET` | Cookie/JWT encryption (`openssl rand -base64 32`) |
| `CALENDSO_ENCRYPTION_KEY` | AES-256 app encryption key (32 bytes) |
| `CALCOM_ENCRYPTION_KEY` / `CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY` | Credential encryption |
| `NEXT_PUBLIC_VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY` | Web Push (generate via `npx web-push generate-vapid-keys`) |

### 6.2 Database & caching

| Variable | Purpose |
| --- | --- |
| `DATABASE_READ_URL` / `DATABASE_WRITE_URL` | Read/write split (API v2) |
| `DATABASE_READ_POOL_MAX` / `DATABASE_WRITE_POOL_MAX` | Pool sizes |
| `USE_POOL` | Enable `pg` connection pooling (`true`/`1`) |
| `DATABASE_CHUNK_SIZE` | Batch sizing |
| `REDIS_URL` | Redis (rate limit, queue, throttler) |
| `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` | Serverless Redis |
| `INSIGHTS_DATABASE_URL` | Read-only replica for analytics (optional; defaults to primary) |
| `SLOTS_CACHE_TTL` | Slot cache lifetime |

### 6.3 Email

| Variable | Purpose |
| --- | --- |
| `EMAIL_FROM` / `EMAIL_FROM_NAME` | Global From header |
| `EMAIL_SERVER_HOST` / `_PORT` / `_USER` / `_PASSWORD` | SMTP server |
| `SENDGRID_API_KEY` / `SENDGRID_EMAIL` / `SENDGRID_SYNC_API_KEY` | SendGrid (workflow reminders) |
| `RESEND_API_KEY` | Resend |
| `SEND_FEEDBACK_EMAIL` | Feedback routing |

### 6.4 Auth, security & rate limiting

| Variable | Purpose |
| --- | --- |
| `JWT_SECRET` | API v2 JWT signing |
| `NEXTAUTH_COOKIE_DOMAIN` | Cookie scope |
| `NEXTAUTH_SECRET_BACKUP` | Rotation support |
| `CSP_POLICY` | Content Security Policy mode (`non-strict`) |
| `CRON_API_KEY` / `CRON_SECRET` | Authenticate cron endpoints |
| `IP_BANLIST` / `BLACKLISTED_GUEST_EMAILS` | Block lists |
| `CLOUDFLARE_TURNSTILE_SECRET` / `NEXT_PUBLIC_CLOUDFLARE_SITEKEY` | Bot protection (Turnstile) |
| `UNKEY_ROOT_KEY` | Optional Unkey rate limiting |
| `RATE_LIMIT_DEFAULT_TTL_MS`, `RATE_LIMIT_DEFAULT_LIMIT`, `_LIMIT_API_KEY`, `_LIMIT_OAUTH_CLIENT`, `_LIMIT_ACCESS_TOKEN` | API v2 rate limits |
| `NODE_TLS_REJECT_UNAUTHORIZED` | Set `0` only behind a trusted SSL-terminating LB |

### 6.5 Platform API (apps/api/v2)

| Variable | Purpose |
| --- | --- |
| `API_PORT` / `API_URL` / `API_ENV` | Server bind/port/env |
| `DATABASE_READ_URL` / `DATABASE_WRITE_URL` | DB endpoints |
| `LOG_LEVEL` | API log level |
| `API_KEY_PREFIX` | API key prefix (e.g. `cal_`) |
| `REWRITE_API_V2_PREFIX` | Rewrite `/api/v2` → `/v2` (default on) |
| `ENABLE_SLOTS_WORKERS` / `SLOTS_WORKER_POOL_SIZE` | Worker-thread slot computation |
| `ENABLE_ASYNC_TASKER` | Async Tasker |
| `DOCS_URL` | Swagger/docs URL |
| `GET_LICENSE_KEY_URL` / `CALCOM_LICENSE_KEY` | (EE remnants; unused in Cal.diy) |

### 6.6 Video & conferencing

| Variable | Purpose |
| --- | --- |
| `DAILY_API_KEY` / `DAILY_SCALE_PLAN` / `DAILY_WEBHOOK_SECRET` | Cal Video (Daily.co) |
| `ZOOM_CLIENT_ID` / `ZOOM_CLIENT_SECRET` | Zoom |
| `MS_GRAPH_CLIENT_ID` / `MS_GRAPH_CLIENT_SECRET` | Teams / Office 365 |
| `HUDDLE01_API_TOKEN` | Huddle01 |
| `CAL_VIDEO_BUCKET_NAME` / `_REGION` / `_ASSUME_ROLE_ARN` | Cal Video recording storage (S3) |
| `B2_APPLICATION_KEY_ID` / `B2_APPLICATION_KEY` / `B2_BUCKET_*` | Backblaze B2 storage |

### 6.7 Payments

| Variable | Purpose |
| --- | --- |
| `STRIPE_PRIVATE_KEY` / `NEXT_PUBLIC_STRIPE_PUBLIC_KEY` / `STRIPE_CLIENT_ID` | Stripe |
| `STRIPE_WEBHOOK_SECRET` / `_APPS` / `_BILLING` | Stripe webhook secrets |
| `PAYMENT_FEE_FIXED` / `PAYMENT_FEE_PERCENTAGE` | Platform fee |

### 6.8 Calendar & integration credentials

| Variable | Purpose |
| --- | --- |
| `GOOGLE_API_CREDENTIALS` / `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | Google Calendar OAuth |
| `GOOGLE_LOGIN_ENABLED` | Enable Google sign-in |
| `GOOGLE_WEBHOOK_TOKEN` / `GOOGLE_WEBHOOK_URL` | Google calendar push |
| `MICROSOFT_WEBHOOK_TOKEN` / `MICROSOFT_WEBHOOK_URL` / `OUTLOOK_LOGIN_ENABLED` | Microsoft calendar |
| `HUBSPOT_*`, `SALESFORCE_*`, `ZOHOCRM_*`, `BASECAMP3_*`, `CLOSECOM_*`, `SLACK_*`, `LARK_*`, `VITAL_*`, `ZAPIER_INVITE_LINK`, … | App-store integration credentials |

### 6.9 Observability & jobs

| Variable | Purpose |
| --- | --- |
| `NEXT_PUBLIC_SENTRY_DSN` / `SENTRY_DSN` / `SENTRY_TRACES_SAMPLE_RATE` / `SENTRY_PROFILES_SAMPLE_RATE` | Sentry |
| `AXIOM_TOKEN` / `AXIOM_DATASET` | Axiom logging |
| `NEXT_PUBLIC_POSTHOG_KEY` / `_HOST` | PostHog |
| `TRIGGER_SECRET_KEY` / `TRIGGER_API_URL` / `TRIGGER_DEV_PROJECT_REF` | trigger.dev background jobs |
| `VERCEL_PROMOTE_WEBHOOK_SECRET` | Verifies Vercel deploy-promoted webhook (HMAC-SHA1) |
| `NEXT_PUBLIC_LOGGER_LEVEL` | tRPC log verbosity (0–6) |

### 6.10 Feature toggles & misc

| Variable | Purpose |
| --- | --- |
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Disable public signup |
| `NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL` | Schedule grid interval (min, default 15) |
| `NEXT_PUBLIC_MINUTES_TO_BOOK` | Slot reservation window |
| `NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS` / `_RESERVATION_INTERVAL_SECONDS` | Booker refetch cadence |
| `NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD` | Booker prefetch window |
| `RESERVED_SUBDOMAINS` / `USERNAME_BLACKLIST_URL` | Username controls |
| `CALCOM_TELEMETRY_DISABLED` | Disable anonymous telemetry |
| `EMBED_PUBLIC_EMBED_VERSION` / `_FINGER_PRINT` / `_WEBAPP_URL` / `_EMBED_LIB_URL` | Embed build vars |

> [!TIP]
> Both `.env` and `.env.appStore` must contain `DATABASE_URL`. App-store-specific OAuth credentials live in `.env.appStore`. The Turbo `env-check` tasks fail the build if expected variables are missing.

---

## 7. Database & Data Model

### 7.1 Prisma setup

The data layer is Prisma 6.16 over PostgreSQL. `packages/prisma/index.ts` builds the client with the `@prisma/adapter-pg` driver adapter and four custom **client extensions**:

| Extension | Effect |
| --- | --- |
| `excludeLockedUsersExtension` | Hides locked users from queries |
| `excludePendingPaymentsExtension` | Hides teams with pending payment |
| `bookingIdempotencyKeyExtension` | Enforces booking idempotency keys |
| `disallowUndefinedDeleteUpdateManyExtension` | Guards against accidental mass `deleteMany`/`updateMany` with `undefined` filters |

```ts
// Read/write split: readonlyPrisma uses INSIGHTS_DATABASE_URL if set, else the primary.
export const readonlyPrisma = process.env.INSIGHTS_DATABASE_URL
  ? customPrisma({ datasources: { db: { url: process.env.INSIGHTS_DATABASE_URL } } })
  : prisma;
```

Connection pooling is opt-in via `USE_POOL=true` (a `pg.Pool` with `max: 5`, 5-minute idle timeout). Prisma's log level is derived from `NEXT_PUBLIC_LOGGER_LEVEL`.

> [!NOTE]
> A custom **enum generator** (`enum-generator.ts`) emits `as const` TypeScript objects for every Prisma enum, so enums are available as plain importable constants and types without pulling in the full Prisma runtime.

### 7.2 Core entities

The schema (`packages/prisma/schema.prisma`) is large; the central entities for scheduling:

| Entity | Role |
| --- | --- |
| `User` | A host. Has username, password (bcrypt), metadata, default conferencing app, plan |
| `EventType` | A bookable offering with all its settings (see User Guide §6.2) |
| `Booking` | A scheduled meeting (uid, attendees, status, location, recurring info, seats) |
| `Attendee` | A guest on a booking |
| `Schedule` / `Availability` | Working-hours definitions and weekly/override slots |
| `SelectedCalendar` | A connected calendar used for conflict-checking + sync state |
| `DestinationCalendar` | Where new events are written |
| `Credential` | Encrypted third-party app credentials |
| `App` | App-store app registration |
| `Webhook` | Outbound webhook subscriptions |
| `ApiKey` | Hashed API keys (prefixed `cal_`) |
| `Feature` / `UserFeatures` / `TeamFeatures` | Feature flags (global + per-user/team) |
| `BookingAudit` / `AuditActor` | Immutable booking audit trail |
| `Team` / `Membership` / `OAuthClient` / `Role` | Org/PBAC scaffolding (EE remnants) |

### 7.3 Event type configuration model

Event type behavior is driven by a large set of scalar fields plus JSON metadata validated by Zod (`packages/prisma/zod-utils.ts`). The "managed event type props" list (`allManagedEventTypeProps`) enumerates every configurable field — confirmation rules, buffers, booking/duration limits, period windows, seats, recurring config, locations, booking fields, colors, time-zone locking, reschedule/cancel locks, and more (see User Guide §6.2 for the catalogue).

Booking-form fields are validated by `fieldSchema`/`baseFieldSchema` with these types: `name`, `text`, `textarea`, `number`, `email`, `phone`, `address`, `multiemail`, `select`, `multiselect`, `checkbox`, `radio`, `radioInput`, `boolean`, `url`. Field editability is governed by an `EditableSchema` enum (`system`, `system-but-optional`, `system-but-hidden`, `user`, `user-readonly`).

### 7.4 Booking Audit System

Cal.diy records every booking change in a tamper-proof audit trail built on two tables:

- **`AuditActor`** — who/what performed an action (types: `USER`, `GUEST`, `ATTENDEE`, `SYSTEM`, `APP`). Uses UUID primary keys and **soft references** (no FK) to `User`/`Attendee` so audits survive deletions. PII fields are nullable for anonymization.
- **`BookingAudit`** — append-only records with UUID v7 (time-sortable) primary keys, a plain-string `bookingUid` (no FK, survives booking deletion), an explicit business `timestamp`, an `operationId` to correlate multi-booking operations, and a JSON `data` field with per-action versioned payloads.

Tracked actions include `CREATED`, `RESCHEDULED`, `ACCEPTED`, `CANCELLED`, `REJECTED`, `RESCHEDULE_REQUESTED`, `ATTENDEE_ADDED/REMOVED`, `REASSIGNMENT`, `LOCATION_CHANGED`, `NO_SHOW_UPDATED`, `SEAT_BOOKED`, `SEAT_RESCHEDULED`. The source channel is one of `WEBAPP`, `API_V1`, `API_V2`, `WEBHOOK`, `SYSTEM`, `UNKNOWN`.

> [!IMPORTANT]
> The audit system is **compliance-oriented**: it satisfies HIPAA §164.312(b) (immutable audit) and GDPR Article 17 (anonymization by nulling `userUuid`/`email`/`phone`/`name`). The processing queue carries **zero PII** — only IDs (`userUuid`/`attendeeId`/`actorId`) — so third-party queue providers can be used safely. Records are immutable and append-only; the database rejects deleting an `AuditActor` that still has audit records (`onDelete: Restrict`).

Architecture: `BookingEventHandlerService` (entry point) → `BookingAuditProducerService` (queues tasks) → `BookingAuditTaskConsumer` (validates, resolves actors, writes immutable records).

### 7.5 Migrations & maintenance scripts

| Script | Purpose |
| --- | --- |
| `db-migrate` / `db-deploy` | Apply schema (dev / prod) |
| `db-seed` | Seed dummy users |
| `seed-app-store` | Populate app-store apps |
| `db-reset` | Full reset (dev) |
| `delete-app.ts <slug>` | Remove an app + its credentials (local) |
| `auto-migrations.ts` | Auto-run `migrate deploy` on boot (skippable) |

---

## 8. The Platform API v2 (Internals)

### 8.1 Bootstrapping

`apps/api/v2/src/main.ts` exposes the app as either a local server or a Vercel serverless handler via a singleton `NestServer`. `bootstrap.ts` configures the application:

- **Versioning:** `VersioningType.CUSTOM` — reads the `cal-api-version` header, validates it against `API_VERSIONS`, and falls back to `VERSION_2024_04_15`.
- **Security:** `helmet()`, CORS with `origin: "*"` and an explicit allowed-header list including `x-cal-client-id`, `x-cal-secret-key`, `x-cal-platform-embed`, and `cal-api-version`.
- **Validation:** a global `ValidationPipe` with `whitelist: true` and `transform: true` (strips unknown fields, coerces types).
- **Exception filters** (ordered): `PrismaExceptionFilter`, `ZodExceptionFilter`, `HttpExceptionFilter`, `TRPCExceptionFilter`, `CalendarServiceExceptionFilter`.
- `cookie-parser`, optional `API_GLOBAL_PREFIX`, custom `qs` query parsing (supports `?ids[]=1&ids[]=2` with `arrayLimit: 1000`).

### 8.2 App module composition

`AppModule` wires:

- `ConfigModule` (global, env-driven via `appConfig`)
- `RedisModule` + `BullModule` (queue; appends `?tls=true` in production)
- `ThrottlerModule.forRootAsync` backed by `ThrottlerStorageRedisService`
- `PrismaModule`, `EndpointsModule` (all resource modules), `AuthModule`, `JwtModule`
- Global providers: `SentryGlobalFilter`, `ResponseInterceptor` (request IDs), `CustomThrottlerGuard`

Middleware order (configured in `AppModule`): `RawBodyMiddleware` (for `/v2/billing/webhook`, `/v2/webhooks/vercel/deployment-promoted`) → `UrlencodedBodyMiddleware` (for `/v2/auth/oauth2/token`) → `JsonBodyMiddleware` (all) → `RequestIdMiddleware` → `AppLoggerMiddleware` → `RedirectsMiddleware` → `RewriterMiddleware`.

### 8.3 Authentication modes

| Mode | Credential | Header(s) |
| --- | --- | --- |
| API key | `cal_…` | `Authorization: Bearer cal_…` |
| OAuth client | client id + secret | `x-cal-client-id`, `x-cal-secret-key` |
| OAuth 2.0 | access token | `Authorization: Bearer <token>` |

The OAuth flow (`oauth-flow.service.ts`) exchanges authorization codes for access/refresh tokens; access tokens are cached in Redis (with a DB fallback). Invalid tokens raise `Invalid Access Token, not present in Redis or DB`.

### 8.4 Rate limiting

`CustomThrottlerGuard` enforces limits backed by Redis. Every response carries `x-ratelimit-limit-{name}`, `x-ratelimit-remaining-{name}`, and `x-ratelimit-reset-{name}` (seconds). Limits can be the global default or **custom per API key**, and multiple named windows (e.g. `short` + `long`) apply simultaneously. Exceeding any window returns **429** until its reset.

> [!NOTE]
> A "dummy" throttler is registered only so NestJS invokes the guard at all — the actual default/custom logic lives in `CustomThrottlerGuard`. Default limit/TTL/block-duration come from `RATE_LIMIT_DEFAULT_*` env vars.

### 8.5 Resource modules

The API is organized into modules under `apps/api/v2/src/modules/*` (account-scoped) and `apps/api/v2/src/platform/*` (versioned platform resources):

| Area | Versions / notes |
| --- | --- |
| Bookings | `2024-04-15`, `2024-08-13` (attendees, guests, seats, references, no-show, reassign, cal-video) |
| Event types | `2024-04-15`, `2024-06-14` (+ transformers internal↔API) |
| Schedules | `2024-04-15`, `2024-06-11` |
| Slots | `2024-04-15`, `2024-09-04` (reserve/release) |
| Calendars | connect, busy times, unified calendars |
| Selected / destination calendars | conflict-check & write targets |
| Conferencing, OOO, timezones, me, users, memberships | account utilities |
| OAuth clients, OAuth2 controllers, atoms, api-keys, stripe, webhooks, verified-resources | platform/dev surface |

> [!TIP]
> In `development`, `main.ts` calls `generateSwaggerForApp(app)`, producing a live OpenAPI document — the authoritative, exact API contract.

### 8.6 Special endpoints

- `GET /health` — version-neutral health check, returns `OK`.
- `POST /v2/webhooks/vercel/deployment-promoted` — HMAC-SHA1-verified (`x-vercel-signature`, `VERCEL_PROMOTE_WEBHOOK_SECRET`) webhook that promotes the matching trigger.dev deployment (with timing-safe comparison and exponential-backoff retries 2s/4s/8s).

---

## 9. Integrations & the App Store

`packages/app-store` is a directory of 100+ integrations. Each app is a workspace package (e.g. `@calcom/googlecalendar`) with a consistent shape: `config.json` (metadata), `api/` (OAuth callbacks + endpoints), `lib/` (service adapters), optional `components/`.

| Category | Apps (examples) |
| --- | --- |
| Calendars | Google, Office365/Outlook, Apple/CalDAV, Exchange 2013/2016/EWS, Lark, Feishu, Zoho, ICS feed |
| Video | Cal Video/Daily, Zoom, Google Meet, Teams, Jitsi, Whereby, Element Call, Huddle01, Mirotalk, Webex, Tandem, Riverside, Sylaps, Salesroom |
| Payments | Stripe, PayPal, HitPay, BTCPay Server, Alby, mock-payment-app |
| CRM | Salesforce, HubSpot, Pipedrive, Zoho CRM/Bigin, Close, Attio |
| Analytics | GA4, GTM, Plausible, Matomo, PostHog, Umami, Fathom, Meta Pixel, Databuddy |
| Automation | Zapier, Make, n8n, Pipedream |
| Messaging | WhatsApp/Twilio, Telegram, Signal, Discord, Basecamp, Intercom |

A code-generation CLI (`@calcom/app-store-cli`) emits `*.generated.ts` barrels from each app's `config.json`/`package.json`/`api`/`components`/`lib`. The `CRON_ENABLE_APP_SYNC` flag keeps app metadata in the DB in sync with config files (reporting-only dry-run when disabled).

### Integration credential setup (OAuth redirect URIs)

Each OAuth integration needs a redirect URI registered with the provider. The pattern is `<Cal.diy URL>/api/integrations/<app>/callback`:

| Provider | Env vars | Redirect URI |
| --- | --- | --- |
| Google Calendar | `GOOGLE_API_CREDENTIALS` | `/api/integrations/googlecalendar/callback` + `/api/auth/callback/google` |
| Microsoft/Office 365 | `MS_GRAPH_CLIENT_ID/SECRET` | `/api/integrations/office365calendar/callback` |
| Zoom | `ZOOM_CLIENT_ID/SECRET` | `/api/integrations/zoomvideo/callback` |
| Daily (Cal Video) | `DAILY_API_KEY` | n/a (API key) |
| Basecamp | `BASECAMP3_CLIENT_ID/SECRET` | `/api/integrations/basecamp3/callback` |
| HubSpot | `HUBSPOT_CLIENT_ID/SECRET` | `/api/integrations/hubspot/callback` |
| ZohoCRM | `ZOHOCRM_CLIENT_ID/SECRET` | `/api/integrations/zohocrm/callback` |

After adding Google credentials, run `cd packages/prisma && yarn seed-app-store` and publish the Google OAuth consent screen.

---

## 10. Calendar Cache & Sync

The **Calendar Cache and Sync** feature (`packages/features/calendar-subscription`) reduces external calendar API calls and keeps availability fresh via provider webhooks instead of polling.

- **Calendar Cache** — stores availability locally (`CalendarCacheEvent` table) to cut external calls.
- **Calendar Sync** — subscribes to provider webhooks (Google, Office365) and applies changes in real time. `SelectedCalendar` is extended with sync state: `channelId`, `channelResourceId`, `channelResourceUri`, `channelKind`, `channelExpiration`, `syncToken`, `syncedAt`, `syncErrorAt`, `syncErrorCount`, `syncSubscribedAt`.

Controlled by feature flags (insert into the `Feature` table, enable per user/team via `UserFeatures`/`TeamFeatures`):

| Flag | Effect |
| --- | --- |
| `calendar-subscription-cache` | Enable calendar caching |
| `calendar-subscription-sync` | Enable sync globally |

Required webhook env vars: `GOOGLE_WEBHOOK_TOKEN`, `MICROSOFT_WEBHOOK_TOKEN` (validate incoming webhooks); `GOOGLE_WEBHOOK_URL`/`MICROSOFT_WEBHOOK_URL` are optional (local-test overrides). Background cron jobs handle cache cleanup and renewing calendar watches.

---

## 11. Security

### 11.1 Secrets & encryption

| Concern | Mechanism |
| --- | --- |
| Cookie/session | `NEXTAUTH_SECRET` (must differ from default) |
| App-level encryption | `CALENDSO_ENCRYPTION_KEY` — AES-256, exactly 32 bytes |
| Stored credentials | Encrypted via `CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY` |
| Service accounts | `CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY` |
| API keys | Hashed in DB, prefixed (`API_KEY_PREFIX`, e.g. `cal_`) |
| Passwords | bcrypt; strength enforced by `isPasswordValid` (upper+lower, digit, length ≥7, ≥15 in strict/admin) |

> [!CAUTION]
> The default placeholder value `secret` for `NEXTAUTH_SECRET`/`CALENDSO_ENCRYPTION_KEY` MUST be replaced before any production use. The encryption key must be identical at build and run time, or previously encrypted data (calendar credentials, tokens) becomes undecryptable.

### 11.2 Content Security Policy

Set `CSP_POLICY="non-strict"` to enable Strict CSP (except `unsafe-inline` in `style-src`). Strict CSP is currently fully enforced only on the login page; other SSR pages run in report-only mode; SSG pages are not yet supported. Custom code may need adjustment for CSP compatibility.

### 11.3 API security

- **helmet** sets security headers; CORS allows all origins with an explicit header allow-list.
- **ValidationPipe** with `whitelist` strips unexpected fields (defense against mass assignment).
- **Rate limiting** per API key / OAuth client / access token via Redis.
- **Webhook signature verification**: Vercel deploy webhook uses HMAC-SHA1 with timing-safe comparison; Stripe/Daily webhooks use their provider secrets (`STRIPE_WEBHOOK_SECRET*`, `DAILY_WEBHOOK_SECRET`).
- **OAuth tokens** are short-lived and stored in Redis with DB fallback; invalid/expired tokens are rejected.

### 11.4 Anti-abuse

| Control | Variable |
| --- | --- |
| Bot detection (Cloudflare Turnstile) | `CLOUDFLARE_TURNSTILE_SECRET`, `NEXT_PUBLIC_CLOUDFLARE_SITEKEY`, `*_USE_TURNSTILE_IN_BOOKER` |
| IP ban list | `IP_BANLIST` |
| Guest email blocklist | `BLACKLISTED_GUEST_EMAILS` |
| Username blocklist | `USERNAME_BLACKLIST_URL`, `RESERVED_SUBDOMAINS` |
| Email/domain require-exclude | per booking-field Zod rules |
| Watchlist | `packages/features/watchlist` (telemetry + DTO) |

### 11.5 Compliance

The Booking Audit System provides HIPAA-aligned immutable audit trails and GDPR Article 17 anonymization with zero-PII queueing (see §7.4). The `bot-detection`, `bookingRequiresAuthentication`, and `requiresBookerEmailVerification` controls limit unauthenticated abuse.

> [!WARNING]
> The API CORS policy is `origin: "*"`. This is appropriate for a public API authenticated by tokens/keys, but ensure no cookie-authenticated endpoints are exposed through it, and place the API behind your own gateway/WAF if you need origin restrictions.

---

## 12. Operations

### 12.1 Start / stop / logs

| Action | Command |
| --- | --- |
| Dev start | `yarn dev` |
| Prod start | `yarn build && yarn start` |
| Docker up/down | `docker compose up -d` / `docker compose down` |
| Tail logs (Docker) | `docker compose logs -f calcom` |
| Set log verbosity | `NEXT_PUBLIC_LOGGER_LEVEL=3` (web), `LOG_LEVEL` (API) |

### 12.2 Database operations

```bash
# Backup (standard Postgres):
pg_dump "$DATABASE_URL" > calcom_backup_$(date +%F).sql

# Restore:
psql "$DATABASE_URL" < calcom_backup_2026-06-05.sql

# Apply migrations (prod):
yarn workspace @calcom/prisma db-deploy

# Inspect data:
yarn db-studio   # Prisma Studio on :5555
```

### 12.3 Email in development

```bash
docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog
# UI: http://localhost:8025 ; required when E2E_TEST_MAILHOG_ENABLED=1
```

### 12.4 Background jobs

- **Tasker** (`packages/features/tasker`) runs async tasks; `ENABLE_ASYNC_TASKER` toggles async mode, `TASKER_ENABLE_WEBHOOKS` enables webhook tasks.
- **trigger.dev** runs durable jobs; configure `TRIGGER_SECRET_KEY`/`TRIGGER_API_URL`/`TRIGGER_DEV_PROJECT_REF`. Deploy via `yarn deploy:trigger:prod` / `:staging`.
- **Cron** endpoints are authenticated with `CRON_API_KEY`/`CRON_SECRET`; app-metadata sync is gated by `CRON_ENABLE_APP_SYNC`.

### 12.5 Monitoring

| Tool | Setup |
| --- | --- |
| **Sentry** | `SENTRY_DSN` (+ traces/profiles sample rates); API initializes Sentry in `instrument.ts` before any module loads |
| **Checkly** | Synthetic checks in `__checks__/**`; `checkly.config.ts` runs from `us-east-1`/`eu-west-1` every 10 min |
| **Axiom** | `AXIOM_TOKEN`/`AXIOM_DATASET` |
| **PostHog** | `NEXT_PUBLIC_POSTHOG_KEY`/`_HOST` |

### 12.6 Health checks

```bash
curl https://your-cal/health           # web/API health
curl http://localhost:5555/v2/me \
  -H "Authorization: Bearer cal_…"      # authenticated round-trip
```

---

## 13. Performance & Scaling

### 13.1 Read/write DB split & pooling

The Platform API supports separate `DATABASE_READ_URL`/`DATABASE_WRITE_URL` with independent pool maxima (`DATABASE_READ_POOL_MAX`, `DATABASE_WRITE_POOL_MAX`, and worker-pool variants). Enable `pg` pooling with `USE_POOL=true`. A read replica for analytics can be set via `INSIGHTS_DATABASE_URL`.

### 13.2 Slot computation workers

Available-slot computation can be offloaded to worker threads: `ENABLE_SLOTS_WORKERS=true` with `SLOTS_WORKER_POOL_SIZE`. Slot results are cached (`SLOTS_CACHE_TTL`). The booker tunes its polling via `NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS`, `NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS`, and `NEXT_PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS`.

### 13.3 Load testing (k6)

`packages/testing/performance` contains a Grafana k6 suite for the booking flow:

```bash
# Direct:
BASE_URL=https://your-cal k6 run tests/performance/smoke/booking.js
# Interactive Docker runner:
./tests/scripts/run-k6-local.sh        # menu: smoke / load / stress / spike / all
```

| Profile | Peak virtual users |
| --- | --- |
| Load | 2,000 |
| Stress | 4,000 |
| Spike | 5,000 |

Thresholds live in `utils/config.js` (strictest for smoke, most lenient for spike).

### 13.4 Caching & queues

Redis backs the throttler storage, BullMQ queues, OAuth-token cache, and slot cache. Calendar Cache & Sync (§10) reduces external API load. In production, BullMQ connects to Redis with TLS.

---

## 14. Internationalization

Locale data lives in `packages/i18n/locales/<locale>/common.json`. The source locale is `en`, with **38 target languages** (`ar`, `de`, `es`, `es-419`, `fr`, `ja`, `ko`, `pt-BR`, `zh-CN`, `zh-TW`, etc.) managed via Lingo.dev (`i18n.json`, `LINGO_DOT_DEV_API_KEY`). An `i18n-unused.config.js` regex finds unused translation keys (`t("…")` and `<Trans i18nKey="…">`).

---

## 15. Testing Strategy

| Layer | Tool | Location |
| --- | --- | --- |
| Unit / integration | Vitest 4 (multi-project workspace) | `packages/**`, `apps/**` `*.{test,spec}.{ts,tsx}` |
| Component | Vitest + jsdom | `packages/ui`, `packages/features/*`, `apps/web/components` |
| Timezone-dependent | Vitest with `TZ` env | `*.timezone.test.ts` (whole suite re-run per TZ) |
| Integration | Vitest (`VITEST_MODE=integration`) | `*.integration-test.ts` |
| E2E | Playwright | `apps/web/playwright`, `packages/app-store`, embeds |
| Embed E2E | Playwright (chromium/firefox/webkit/mobile) | `packages/embeds/embed-core|react` on ports 3100/3101 |
| Performance | k6 | `packages/testing/performance` |
| API (NestJS) | Jest + supertest | `apps/api/v2/**/*.e2e-spec.ts` |
| Synthetic | Checkly | `__checks__/**` |

```bash
yarn test                          # unit/integration (vitest)
yarn test -t getSchedule           # filter by name
yarn test-e2e                      # Playwright
yarn playwright show-report test-results/reports/playwright-html-report
npx playwright install             # download browsers if missing
```

Playwright timeouts are deliberately high locally (120s nav/expect, 240s test) and tight in CI (10s). The integration-test DB runs Postgres 18 in `apps/web/test/docker-compose.yml` on port 5433.

---

## 16. Repository Structure (for Developers)

| Path | Contents |
| --- | --- |
| `apps/web` | Next.js app (App Router + `pages/`, `modules/`, `components/`, `lib/`, `server/`) |
| `apps/api/v2` | NestJS Platform API (`src/modules`, `src/platform`, `src/middleware`, `src/lib`, `src/filters`) |
| `apps/docs` | Nextra docs site |
| `packages/features` | Domain logic by feature (bookings, availability, eventtypes, auth, webhooks, schedules, calendars, conferencing, data-table, flags, tasker, …) |
| `packages/lib` | Shared utilities (dependency floor) |
| `packages/trpc` | tRPC routers (web app's internal API) |
| `packages/prisma` | Schema, migrations, seed, extensions, generated client |
| `packages/app-store` | Integrations + app-store CLI |
| `packages/platform/*` | atoms SDK, platform types/constants/enums/libraries |
| `packages/embeds/*` | Embed core / react / snippet |
| `packages/ui`, `packages/emails`, `packages/i18n` | Design system, email templates, locales |
| `specs/` | Spec-first design docs (Claude-assisted feature workflow) |

> [!NOTE]
> `specs/` documents a "spec-first development" workflow where design docs (`design.md`, `implementation.md`, `decisions.md`) are authored before code, with a rule that every PR be reviewable in under 10 minutes (≤5–7 files, ≤500 lines). This is a contributor-process artifact, not a runtime component.

### Coding standards

Biome enforces formatting (110-col, 2-space, LF, double quotes, semicolons, trailing ES5 commas) and the import-boundary rules in §2.2. Import organization is grouped (`@calcom/**`, then `@lib/@components/@server/@trpc`, then `~`, then relative).

---

## 17. Operational Troubleshooting

### `CLIENT_FETCH_ERROR` in Docker
NextAuth can't resolve `WEBAPP_URL` from inside the container. Set `NEXTAUTH_URL=http://localhost:3000/api/auth` so the backend loops back to itself.

### `No key set vapidDetails.publicKey`
Web Push keys missing. Generate with `npx web-push generate-vapid-keys`; set `NEXT_PUBLIC_VAPID_PUBLIC_KEY` and `VAPID_PRIVATE_KEY`.

### Requests rejected behind a load balancer
SSL edge termination: set `NODE_TLS_REJECT_UNAUTHORIZED=0` **only** if you trust the LB.

### `Invalid 'prisma.user.create()'`
Set `metadata` to `{}` and leave `id` empty (autoincrement).

### Prisma `unexpected character / in variable name` (Windows)
Replace the `packages/prisma/.env` symlink with a real copy: `rm packages/prisma/.env && cp .env packages/prisma/.env`.

### Build OOM
Increase Node heap: `export NODE_OPTIONS="--max-old-space-size=16384"`; ensure `MAX_OLD_SPACE_SIZE` is set for the Docker build.

### API 429s
Inspect `x-ratelimit-*` headers; raise the per-key custom limit or default `RATE_LIMIT_DEFAULT_*`.

### Migrations not applying on boot
`auto-migrations.ts` skips if `DATABASE_URL`/`DATABASE_DIRECT_URL` are unset, Prisma can't connect, or `SKIP_DB_MIGRATIONS=1`. Run `db-deploy` manually.

### Calendar sync not updating
Confirm the `calendar-subscription-sync` feature flag is enabled and `GOOGLE_WEBHOOK_TOKEN`/`MICROSOFT_WEBHOOK_TOKEN` are set; check `syncErrorAt`/`syncErrorCount` on the `SelectedCalendar` row.

---

## 18. Potential Improvements & Tech Debt

These observations are drawn from the source as of the documented commit:

- **EE remnants:** The schema and Platform API retain team/organization/PBAC scaffolding (`Team`, `Membership`, `OAuthClient`, `Role`, PBAC seed scripts) even though enterprise features are "removed." A self-hoster cannot use these features but the code paths and DB tables remain — a source of confusion and dead surface area.
- **Build-time DB dependency:** The Docker build requires a reachable database, complicating CI. The README notes this requirement "will be removed in the future."
- **`DOCKER_BUILDKIT=0` requirement:** Building from source disables BuildKit to allow a build-time network bridge — a workaround that should be eliminated.
- **CSP maturity:** Strict CSP is enforced only on the login page; other SSR pages are report-only and SSG pages unsupported. Full CSP rollout is incomplete.
- **CORS `origin: "*"`:** Acceptable for a token-authenticated API but warrants a configurable allow-list for stricter deployments.
- **Encryption-key coupling:** `CALENDSO_ENCRYPTION_KEY` must match between build and run; a mismatch silently breaks decryption of stored credentials. A key-rotation story would reduce risk.
- **Method-not-implemented stubs:** Some payment adapters (e.g. HitPay) contain `Method not implemented` stubs — incomplete provider coverage.
- **Telemetry default:** Anonymous telemetry is on unless `CALCOM_TELEMETRY_DISABLED=1` — document this prominently for privacy-sensitive deployments.

---

## 19. Quick Reference

### Ports

| Port | Service |
| --- | --- |
| 3000 | Web app |
| 5555 | Prisma Studio / Platform API |
| 5433 | Integration-test Postgres |
| 1025 / 8025 | MailHog SMTP / UI |
| 3100 / 3101 | embed-core / embed-react E2E |

### Critical env vars (must set for production)

```env
DATABASE_URL=postgresql://user:pass@host:5432/calendso
NEXT_PUBLIC_WEBAPP_URL=https://your-cal
NEXTAUTH_URL=https://your-cal/api/auth
NEXTAUTH_SECRET=<openssl rand -base64 32>
CALENDSO_ENCRYPTION_KEY=<openssl rand -base64 24>   # 32 bytes
NEXT_PUBLIC_VAPID_PUBLIC_KEY=<npx web-push generate-vapid-keys>
VAPID_PRIVATE_KEY=<...>
REDIS_URL=redis://...                                # for Platform API
```

### Key commands

| Command | Purpose |
| --- | --- |
| `yarn dx` | DB + app with seeds (Docker) |
| `yarn dev` / `yarn build && yarn start` | Dev / prod |
| `yarn workspace @calcom/prisma db-deploy` | Apply migrations (prod) |
| `yarn db-studio` | Prisma Studio |
| `cd packages/prisma && yarn seed-app-store` | Repopulate app store |
| `yarn test` / `yarn test-e2e` | Unit / E2E tests |
| `./tests/scripts/run-k6-local.sh` | Performance tests |
| `yarn deploy:trigger:prod` | Deploy trigger.dev jobs |

### Platform API essentials

| Item | Value |
| --- | --- |
| Base prefix | `/v2` (`/api/v2` rewritten) |
| Default version | `2024-04-15` (header `cal-api-version`) |
| Auth | `Authorization: Bearer cal_…` / `x-cal-client-id`+`x-cal-secret-key` / OAuth2 token |
| Health | `GET /health` → `OK` |
| Rate-limit headers | `x-ratelimit-{limit,remaining,reset}-{name}` |
| Docs | Swagger generated in `development` |

### Architecture one-liner

A Yarn-4/Turborepo monorepo: a Next.js+tRPC scheduling web app and a NestJS Platform API share a Prisma/PostgreSQL data layer and Redis, surrounded by an embed SDK, a React component SDK (`@calcom/atoms`), and a 100+ app integration store — all MIT-licensed, self-hosted, with enterprise features removed.

---

_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._
