Cal.com

Read, book, move and cancel Cal.com bookings as calendar events.

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 install @cal-sdk/core @cal-sdk/calcom
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

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

Settings

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

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

All-day and recurring events, ifMatch and search throw UnsupportedFunctionalityError. Free/busy is computed from bookings. See the comparison for every provider.

How it behaves

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

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

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

eventTypeId?number
Required on 'primary'.
metadata?Record<string, string>
Stored on the booking.
allowConflicts?boolean= true
Book over other bookings and busy time.
allowBookingOutOfBounds?boolean= true
Book outside the event type's availability, notice and booking window.

updateEvent

reschedulingReason?string
Shown in the reschedule email.
allowConflicts?boolean= true
As on createEvent.
allowBookingOutOfBounds?boolean= true
As on createEvent.

deleteEvent

cancellationReason?string= 'Cancelled'
Shown in the cancellation email.

Metadata

Events carry providerMetadata.calcom:

FieldType and notes
bookingIdnumber, Cal.com's numeric id. The event id is the uid
eventTypeIdnumber
statusstring: 'accepted', 'pending' or 'awaiting_host'
rescheduledFromUidstring, the booking this one replaced
metadataRecord<string, string>

On this page