# Write events (https://www.cal-sdk.com/docs/guides/write-events)

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 [#create-an-event]

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

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:

```ts
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 [#invite-people]

```ts
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](/docs/providers) says what it does.

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

## Add a video link [#add-a-video-link]

```ts
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 [#update-an-event]

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

```ts
import { updateEvent } from '@cal-sdk/core';

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 [#delete-an-event]

```ts
import { deleteEvent } from '@cal-sdk/core';

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 [#dont-overwrite-someone-elses-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:

```ts
import { ConflictError, getEvent, updateEvent } from '@cal-sdk/core';

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 [#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:

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