Quickstart

Create a recurring event, move one occurrence and find free time, with no account and no network.

Start with a calendar you can't break: @calsdk/mock, which keeps events in memory. You'll create a weekday standup, move Wednesday's to later in the morning, and find a free slot around it.

Install

npm install calsdk @calsdk/mock

Create a recurring event

Save this as quickstart.ts:

quickstart.ts
import { createEvent, findFreeTime, listEvents, updateEvent } from 'calsdk';
import { mockCalendar } from '@calsdk/mock';

const calendar = mockCalendar();

const { event: standup } = await createEvent({
  calendar,
  event: {
    title: 'Standup',
    start: { dateTime: '2026-11-09T09:30:00', timeZone: 'Europe/Berlin' },
    end: { dateTime: '2026-11-09T09:45:00', timeZone: 'Europe/Berlin' },
    recurrence: ['RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR'],
  },
});

console.log(standup.kind, standup.start.dateTime);
// series 2026-11-09T09:30:00+01:00

You didn't pass calendarId, so the event went to 'primary', the user's default calendar. And because the dateTime had no offset, calsdk read it as 9:30 on the Berlin wall clock and returned it with the offset filled in.

List a week

quickstart.ts
const { events } = await listEvents({
  calendar,
  timeMin: '2026-11-09T00:00:00+01:00',
  timeMax: '2026-11-14T00:00:00+01:00',
});

for (const event of events) console.log(event.id, event.kind, event.start.dateTime);
// evt_1_20261109T083000Z occurrence 2026-11-09T09:30:00+01:00
// evt_1_20261110T083000Z occurrence 2026-11-10T09:30:00+01:00
// evt_1_20261111T083000Z occurrence 2026-11-11T09:30:00+01:00
// evt_1_20261112T083000Z occurrence 2026-11-12T09:30:00+01:00
// evt_1_20261113T083000Z occurrence 2026-11-13T09:30:00+01:00

listEvents expands recurring events by default, so the series comes back as one occurrence per weekday. Each has its own id, which is what you pass to change a single day, and seriesId points back at the series.

Move one occurrence

quickstart.ts
const wednesday = events[2];

const { event: moved } = await updateEvent({
  calendar,
  eventId: wednesday.id,
  patch: {
    start: { dateTime: '2026-11-11T11:00:00', timeZone: 'Europe/Berlin' },
    end: { dateTime: '2026-11-11T11:15:00', timeZone: 'Europe/Berlin' },
  },
});

console.log(moved.kind, moved.start.dateTime);
// exception 2026-11-11T11:00:00+01:00

Updating an occurrence changes only that one by default, and it becomes an exception: an occurrence that no longer follows the series rule. Pass scope: 'all' to change the whole series instead. Recurring events covers the other scopes.

Find free time

quickstart.ts
const { slots } = await findFreeTime({
  calendars: [{ calendar }],
  timeMin: '2026-11-09T00:00:00+01:00',
  timeMax: '2026-11-10T00:00:00+01:00',
  duration: 30,
  slotInterval: 30,
  workingHours: { start: '09:00', end: '11:00', timeZone: 'Europe/Berlin' },
});

console.log(slots.map((slot) => slot.start));
// [ '2026-11-09T08:00:00.000Z', '2026-11-09T09:00:00.000Z', '2026-11-09T09:30:00.000Z' ]

Monday's standup blocks the 9:30 slot, so the free half hours are 9:00, 10:00 and 10:30 in Berlin. Slots come back as UTC instants, which is why 9:00 in Berlin prints as 08:00:00.000Z.

Run it

Terminal
bun quickstart.ts
# or: npx tsx quickstart.ts

Along with your own output, you'll see this line:

Output
calsdk warning (mock, getFreeBusy): freeBusy uses a fallback: Provider "mock" has no free/busy endpoint, so busy times were computed from events.

That's a warning, not an error. The mock has no free/busy endpoint, so calsdk worked out busy times from its events and tells you it did. calsdk does the same for Outlook, so expect a similar line there.

Use a real calendar

When the logic does what you want, install a provider and swap it in for the mock. Only the line that creates calendar changes; every call after it stays the same.

// npm install @calsdk/google
import { google } from '@calsdk/google';

const calendar = google({ accessToken: () => getFreshAccessToken() });

Each provider page, linked from Choose a provider, says which token or password it needs and where it behaves differently.

Use a test calendar

Writes go to the real account. Point calendarId at a calendar made for testing until your code does what you expect.

On this page