Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Cal.diy Admin Guide

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.

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
  2. Architecture Overview
  3. Technology Stack
  4. System Requirements
  5. Installation & Deployment
  6. Configuration Reference
  7. Database & Data Model
  8. The Platform API v2 (Internals)
  9. Integrations & the App Store
  10. Calendar Cache & Sync
  11. Security
  12. Operations
  13. Performance & Scaling
  14. Internationalization
  15. Testing Strategy
  16. Repository Structure (for Developers)
  17. Operational Troubleshooting
  18. Potential Improvements & Tech Debt
  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

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:

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

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

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

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)

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:

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

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
// 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

# 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

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

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:

# 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
Performance k6 packages/testing/performance
API (NestJS) Jest + supertest apps/api/v2/**/*.e2e-spec.ts
Synthetic Checkly __checks__/**
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)

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.