Microsoft Outlook

Read and write Outlook and Microsoft 365 calendars through Microsoft Graph, with Teams meetings and delta sync.

Use this provider for Outlook and Microsoft 365 calendars, on work, school and personal Microsoft accounts. It covers most of what Google does, with three differences you'll feel: Outlook stores recurrence as patterns rather than RRULEs, has no "this and following", and syncs a fixed window of time.

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

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

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

Credentials

accessToken is a Microsoft Graph token with Calendars.ReadWrite, or Calendars.Read if you only read. calsdk doesn't run the OAuth flow: get tokens from your auth library and pass a function that returns a fresh one. calsdk calls it before every request.

Settings

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

Not supported

Outlook has no thisAndFollowing, event colors, search or free/busy endpoint. Search throws UnsupportedFunctionalityError, so filter the listed events instead, and calsdk computes free/busy from events. See the comparison for every provider.

How it behaves

Recurrence patterns

Outlook stores recurrence as patterns, not RRULEs. Rules with an exact Outlook pattern are converted both ways. Others, such as RDATE, FREQ=HOURLY or BYMONTHDAY=31 alone, throw UnsupportedFunctionalityError, because saving them would quietly schedule a different set of meetings. One difference shows up when reading: Outlook moves a day a month lacks, such as the 31st, to the month's last day, so that rule reads back as BYMONTHDAY=28,29,30,31;BYSETPOS=-1.

Outlook also keeps no list of excluded dates, only occurrences deleted one by one. calsdk reads those back as EXDATE lines on the series, which costs one extra request per series. To exclude a new date, delete that occurrence instead of adding an EXDATE.

No "this and following"

thisAndFollowing throws UnsupportedFunctionalityError. You can still move a whole series from one of its occurrences: with scope all, the series moves by the same wall-clock distance. On a monthly or yearly series that throws too, since its pattern picks days by date and would land on different ones.

A fixed sync window

Sync uses Graph delta queries, which work over a date range. So a sync covers a fixed window, by default one year back and two years ahead of the first sync, and the cursor keeps it. Changes cover single events, series and changed occurrences. Deleting one occurrence changes its series, so it arrives as an update of the series with a new EXDATE.

Ids and time zones

calsdk asks Graph for immutable ids, so an event keeps its id when someone moves it to another calendar, and the ids you've stored stay valid. Times keep the zone they were scheduled in; Outlook names zones the Windows way ("Pacific Standard Time"), and calsdk turns those into IANA names.

Writing events

Outlook ignores If-Match on deletes, so deleteEvent with ifMatch compares the etag first. That leaves a small gap: a change made between the check and the delete isn't detected.

createConference: true asks for a Teams meeting. Personal accounts, and some work accounts, can't create one through Graph, so on those the event is saved without it and the result carries a warning. Outlook also always notifies attendees, so sendUpdates values other than 'all' return a warning.

Outlook allows one popup reminder per event, and it has no default reminder to go back to. So reminders: null and useDefault: true leave the reminder unchanged, with a warning. To turn it off, pass { useDefault: false, overrides: [] }. Events have no color of their own either, only categories, which carry Outlook's colors. color is ignored with a warning, so use the categories option instead.

Provider options

await createEvent({
  calendar,
  event,
  providerOptions: {
    microsoft: { showAs: 'oof', importance: 'high', categories: ['Travel'], transactionId: 'trip-42' },
  },
});

await syncEvents({
  calendar,
  providerOptions: {
    microsoft: { timeMin: '2026-01-01T00:00:00Z', timeMax: '2027-01-01T00:00:00Z' },
  },
});

createEvent and updateEvent

showAs?'free' | 'tentative' | 'busy' | 'oof' | 'workingElsewhere'
Overrides showAs on the event.
importance?'low' | 'normal' | 'high'
categories?string[]
Outlook category names. Categories carry Outlook's colors.
transactionId?string
Makes a create safe to retry: Outlook won't create it twice.

syncEvents

timeMin?string
ISO 8601 instant with an offset. Where a first sync's window starts.
timeMax?string
ISO 8601 instant with an offset. Where a first sync's window ends.

Metadata

Events carry providerMetadata.microsoft:

FieldType and notes
showAsstring: Outlook's own 'free', 'tentative', 'busy', 'oof', 'workingElsewhere' or 'unknown'
importancestring
categoriesstring[]
iCalUIdstring

On this page