# Cal.com (https://www.cal-sdk.com/docs/providers/calcom)

Cal.com is a scheduling tool, not a calendar, so here the events are bookings. Use this provider to read a user's bookings, book a slot on one of their event types, reschedule it or cancel it, with the same functions you'd use for Google. Expect the booking rules to win: the event type decides the title, the length and most other fields.

```npm
npm install @cal-sdk/core @cal-sdk/calcom
```

```ts
import { createEvent, listEvents } from '@cal-sdk/core';
import { calcom } from '@cal-sdk/calcom';

const calendar = calcom({ accessToken: process.env.CALCOM_API_KEY! });

const { events } = await listEvents({
  calendar,
  timeMin: '2026-11-01T00:00:00Z',
  timeMax: '2026-12-01T00:00:00Z',
});

// Book event type 123456 for a guest.
await createEvent({
  calendar,
  calendarId: '123456',
  event: {
    start: { dateTime: '2026-11-03T10:00:00', timeZone: 'Europe/Berlin' },
    end: { dateTime: '2026-11-03T10:30:00', timeZone: 'Europe/Berlin' },
    attendees: [{ email: 'guest@example.com', name: 'Guest' }],
  },
});
```

## Credentials [#credentials]

`accessToken` is a Cal.com API key (`cal_live_...`) or an OAuth access token.

## Settings [#settings]

<TypeTable
  type={{
    accessToken: { type: 'string | (() => string | PromiseLike<string>)', description: 'API key or OAuth access token, or a function that returns one.', required: true },
    baseURL: { type: 'string', description: "Use 'https://api.cal.eu/v2' for Cal.com EU.", default: "'https://api.cal.com/v2'" },
    headers: { type: 'Record<string, string | undefined>', description: 'Headers sent with every request.' },
    fetch: { type: 'typeof fetch', description: 'A custom fetch, for example to log or record requests.' },
  }}
/>

## Calendars [#calendars]

`'primary'` holds every booking of the user. Each event type is also a calendar, whose id is the event type id (such as `'123456'`), holding that event type's bookings.

## Not supported [#not-supported]

All-day and recurring events, `ifMatch` and search throw `UnsupportedFunctionalityError`. Free/busy is computed from bookings. See the [comparison](/docs/providers) for every provider.

## How it behaves [#how-it-behaves]

### Booking [#booking]

Creating an event books an event type: the calendar's, or `providerOptions.calcom.eventTypeId` on `'primary'`. The first attendee books the slot and the others are added as guests. Without attendees, the user books it for themselves. The description becomes the booking's notes. Cal.com names bookings after their event type, so `title`, and fields the event type sets (`location`, `createConference`, `reminders`, `color`, `visibility`), are ignored with a warning.

A booking lasts as long as its event type, or one of the lengths the event type offers. Other lengths throw `InvalidArgumentError`. As with any other calendar, calsdk writes the times you give it, so by default a booking may overlap others and fall outside the event type's availability. To have Cal.com enforce its rules, set `allowConflicts` or `allowBookingOutOfBounds` to `false`.

### Moving and cancelling [#moving-and-cancelling]

An event id is the booking's `uid`, and that id doesn't survive a move. Updating can only move a booking, and Cal.com moves a booking by replacing it, so the returned event has a **new id** and the old id reads as deleted. If you store booking ids, update them after every move. Changes to other fields are ignored with a warning, and the booking keeps the time zone it was made in.

Deleting cancels the booking. Cancelled and rejected bookings read as deleted events, and bookings waiting for confirmation are `tentative`. Cal.com always emails attendees, so `sendUpdates` values other than `'all'` return a warning.

### Listing and sync [#listing-and-sync]

Listing finds bookings by their start, because Cal.com can't filter them by their end. To catch bookings that started before `timeMin` and are still running, calsdk looks back as far as the longest length your event types allow (at least a day). A booking that started before that lookback is missed: one of a team event type you don't own, or one longer than its event type now allows.

Sync reads bookings by the time they last changed. A booking saved in a slow transaction can show up after later ones, so each sync reads the minute before the newest change it saw again, and that booking isn't missed. A first sync returns active bookings only.

## Provider options [#provider-options]

```ts
await createEvent({
  calendar,
  event,
  providerOptions: {
    calcom: { eventTypeId: 123456, metadata: { orderId: '42' }, allowConflicts: false },
  },
});

await updateEvent({
  calendar,
  eventId,
  patch: { start, end },
  providerOptions: { calcom: { reschedulingReason: 'Travel' } },
});

await deleteEvent({
  calendar,
  eventId,
  providerOptions: { calcom: { cancellationReason: 'No longer needed' } },
});
```

### createEvent [#createevent]

<TypeTable
  type={{
    eventTypeId: { type: 'number', description: "Required on `'primary'`." },
    metadata: { type: 'Record<string, string>', description: 'Stored on the booking.' },
    allowConflicts: { type: 'boolean', description: 'Book over other bookings and busy time.', default: 'true' },
    allowBookingOutOfBounds: { type: 'boolean', description: "Book outside the event type's availability, notice and booking window.", default: 'true' },
  }}
/>

### updateEvent [#updateevent]

<TypeTable
  type={{
    reschedulingReason: { type: 'string', description: 'Shown in the reschedule email.' },
    allowConflicts: { type: 'boolean', description: 'As on `createEvent`.', default: 'true' },
    allowBookingOutOfBounds: { type: 'boolean', description: 'As on `createEvent`.', default: 'true' },
  }}
/>

### deleteEvent [#deleteevent]

<TypeTable
  type={{
    cancellationReason: { type: 'string', description: 'Shown in the cancellation email.', default: "'Cancelled'" },
  }}
/>

## Metadata [#metadata]

Events carry `providerMetadata.calcom`:

| Field                | Type and notes |
| -------------------- | ---- |
| `bookingId`          | `number`, Cal.com's numeric id. The event id is the `uid` |
| `eventTypeId`        | `number` |
| `status`             | `string`: `'accepted'`, `'pending'` or `'awaiting_host'` |
| `rescheduledFromUid` | `string`, the booking this one replaced |
| `metadata`           | `Record<string, string>` |
