Write events

Create, update and delete events, invite attendees, add video links and avoid overwriting changes.

You want every booking in your app to land in your users' real calendars, whichever one they use. With calsdk, each booking is one createEvent call.

Create an event

import { createEvent } from 'calsdk';

const { event, warnings } = await createEvent({
  calendar,
  event: {
    title: 'Design review',
    description: 'Walk through the new onboarding flow.',
    location: 'Room 4',
    start: { dateTime: '2026-11-03T10:00:00', timeZone: 'Asia/Kolkata' },
    end: { dateTime: '2026-11-03T11:00:00', timeZone: 'Asia/Kolkata' },
  },
});

You get back the event as the provider stored it, with its id, and an etag where the provider supports version checks. Check warnings for anything the provider changed or couldn't save, such as a color Google doesn't have.

All-day events use dates instead, and the end date is exclusive:

await createEvent({
  calendar,
  event: { title: 'Offsite', start: { date: '2026-11-12' }, end: { date: '2026-11-14' } },
});
// Covers the 12th and the 13th.

calsdk checks every event before it calls the provider, so a start after its end, an unknown time zone or a malformed RRULE throws InvalidArgumentError straight away, before any request.

Invite people

await createEvent({
  calendar,
  event: {
    title: 'Kickoff',
    start,
    end,
    attendees: [
      { email: 'ana@example.com', name: 'Ana' },
      { email: 'raj@example.com', optional: true },
    ],
  },
  sendUpdates: 'all',
});

sendUpdates decides who gets an email: 'all', 'externalOnly' (people outside the organization) or 'none'. Leave it out to get the provider's default. Not every provider lets you choose: some always email attendees, and some never do. Cal.com, for example, always emails attendees about their bookings, so asking for 'none' there still sends the email and returns a warning that says so. Each provider page says what it does.

When you read events back, attendees carry their responseStatus (needsAction, accepted, declined or tentative), and organizer says who organized it.

const { event } = await createEvent({
  calendar,
  event: { title: 'Sync', start, end, createConference: true },
});

console.log(event.conference);
// { provider: 'google-meet', joinUrl: 'https://meet.google.com/...', id: '...' }

Google adds a Meet link and Outlook a Teams meeting (work and school accounts only). Every Zoom meeting has its link already. Other providers can't create links, but CalDAV and ICS events often have one pasted in, so calsdk looks for a Meet, Teams or Zoom link in the event's URL, location or description and returns it as conference.

Update an event

updateEvent takes a patch. Fields you leave out stay as they are, and null clears a field:

import { updateEvent } from 'calsdk';

const { event } = await updateEvent({
  calendar,
  eventId,
  patch: { title: 'Design review (moved)', location: null },
});

start and end can be replaced but not cleared. Always use the id of the returned event afterwards, because a few providers replace an event when it moves. Cal.com, for one, cancels the old booking and gives you a new one with a new id.

Delete an event

import { deleteEvent } from 'calsdk';

await deleteEvent({ calendar, eventId });

A missing event throws NotFoundError. The exception is a retry: if an earlier attempt deleted the event but its response got lost, calsdk treats the delete as done instead of failing.

Don't overwrite someone else's change

You read a meeting, a colleague renames it in Outlook, and your update quietly overwrites their change. To prevent that, pass the event's etag (a version tag, on providers that have one) as ifMatch. The update then only goes through if nobody changed the event since you read it:

import { ConflictError, getEvent, updateEvent } from 'calsdk';

const { event } = await getEvent({ calendar, eventId });

try {
  await updateEvent({ calendar, eventId, patch: { title: 'Renamed' }, ifMatch: event.etag });
} catch (error) {
  if (!ConflictError.isInstance(error)) throw error;
  // Someone else changed it. Read it again and decide what to do.
}

deleteEvent takes ifMatch too. Outlook ignores it on deletes, so calsdk reads the event, compares the etag and then deletes. A change made between the read and the delete isn't caught.

Google, Outlook, CalDAV and the mock support ifMatch. Cal.com, Calendly and Zoom throw UnsupportedFunctionalityError when you pass it.

Retries

calsdk retries rate limits, timeouts, server errors and network failures twice by default, with exponential backoff that honors Retry-After. A Retry-After over a minute isn't waited out: calsdk throws so the call doesn't hang. Set maxRetries to change the count, or 0 to turn retries off.

Creates are the exception. They only retry rate limits, which the provider rejects before doing any work, because retrying after a server error could create the event twice. To make an Outlook create safe to retry yourself, pass a transactionId, and Outlook won't create the event again on a repeat:

await createEvent({
  calendar,
  event,
  providerOptions: { microsoft: { transactionId: order.id } },
});

On this page