Google Calendar

Read and write Google Calendar events, with Meet links, free/busy and sync tokens.

Google is the most complete provider: it supports everything calsdk models, including splitting a recurring series with thisAndFollowing, Meet links, search and a real free/busy endpoint. If your users are mostly on Google Workspace or Gmail, this is the one you'll spend the least time working around.

npm install @cal-sdk/core @cal-sdk/google
import { listEvents } from '@cal-sdk/core';
import { google } from '@cal-sdk/google';

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

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

Credentials

accessToken is an OAuth access token with a Calendar scope, such as https://www.googleapis.com/auth/calendar, or calendar.readonly if you only read. calsdk doesn't run the OAuth flow, since your app almost certainly has an auth library already. Get tokens there and pass a function that returns a fresh one: calsdk calls it before every request, so a token that expires mid-sync isn't a problem.

Settings

accessTokenstring | (() => string | PromiseLike<string>)
OAuth access token, or a function that returns a fresh one.
baseURL?string= 'https://www.googleapis.com/calendar/v3'
headers?Record<string, string | undefined>
Headers sent with every request.
fetch?typeof fetch
A custom fetch, for example to log or record requests.

How it behaves

Splitting a series

Google's API has no "this and following" operation, so calsdk splits the series itself for thisAndFollowing. It starts a new series at the chosen instance, copying attendees and the meeting link, and ends the old one just before it. A COUNT is split between the two, and EXDATE and RDATE lines go to the half they fall in. If ending the old series fails, calsdk deletes the new one so you're not left with both.

One case can't be done safely. A series with a COUNT and EXDATE or RDATE lines before the split throws UnsupportedFunctionalityError, because calsdk can't count the occurrences that are left. Give the series an UNTIL first.

Moving a series

A new start with scope all or thisAndFollowing moves the series on the wall clock of its time zone, so a 9:00 standup stays at 9:00 across daylight saving changes. Rules that pick days of the month or year can't be moved this way and throw UnsupportedFunctionalityError. You also can't change a series' time zone through one of its instances: update the series id instead.

Google resets single-instance changes when a series moves or splits. calsdk keeps the occurrences you deleted, as EXDATEs, but edited ones lose their changes, and you get a compatibility warning.

When reading, Google's expanded listings don't say which instances of a series were edited. To tell an exception from a plain occurrence, calsdk makes one extra request per page that has recurring instances.

createConference: true adds a Google Meet link. Colors are less flexible: Google has a fixed palette of 11 event colors, not arbitrary hex values, so if you pass another color, calsdk saves the nearest one and returns a compatibility warning.

Free/busy and sync

Free/busy uses Google's own endpoint, so events marked as free are ignored, and calendars the user can only see as free/busy still work. Sync uses Google sync tokens. An expired one throws SyncCursorExpiredError, and you start over with a full sync.

Provider options

await createEvent({
  calendar,
  event,
  providerOptions: {
    google: {
      guestsCanModify: true,
      guestsCanInviteOthers: false,
      guestsCanSeeOtherGuests: false,
      extendedProperties: { private: { orderId: '42' } },
    },
  },
});

await listEvents({
  calendar,
  timeMin,
  timeMax,
  providerOptions: { google: { eventTypes: ['default'] } },
});

createEvent and updateEvent

guestsCanModify?boolean
Guests can edit the event.
guestsCanInviteOthers?boolean
Guests can invite other people.
guestsCanSeeOtherGuests?boolean
Guests can see the guest list.
extendedProperties?{ private?: Record<string, string>; shared?: Record<string, string> }
private is only visible on this calendar.

listEvents

eventTypes?('birthday' | 'default' | 'focusTime' | 'fromGmail' | 'outOfOffice' | 'workingLocation')[]
Only return these types. By default Google returns every type.

Metadata

Events carry providerMetadata.google:

FieldType and notes
iCalUIDstring
eventTypestring, such as 'default', 'outOfOffice' or 'focusTime'
extendedProperties{ private?: Record<string, string>; shared?: Record<string, string> }

On this page