---
title: "Vue Agenda Outlook Calendar Sync Example | Mobiscroll"
component: "Agenda"
framework: "Vue"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/vue/agenda/sync-events-outlook-calendar"
---

# Outlook Calendar Sync - Vue Agenda demo

Use the Vue event calendar integration plugin to authenticate users, load their Outlook calendars, and display events in an agenda view. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/agenda/sync-events-outlook-calendar)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/eventcalendar/agenda/sync-events-outlook-calendar)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/eventcalendar/agenda/sync-events-outlook-calendar)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/agenda/sync-events-outlook-calendar.md), [jQuery](https://demo.mobiscroll.com/jquery/agenda/sync-events-outlook-calendar.md), [Angular](https://demo.mobiscroll.com/angular/agenda/sync-events-outlook-calendar.md), [React](https://demo.mobiscroll.com/react/agenda/sync-events-outlook-calendar.md)

## Description

The event calendar comes with third party calendar integration support through the [**integration plugin**](https://mobiscroll.com/docs/vue/eventcalendar/calendar-integrations). This needs to be added to the download package or installed separately from a dedicated NPM package.

It includes everything you need to authenticate your users, get their Outlook calendars and provides functions for CRUD operations. It takes care of loading the events and converts them to the required format so that they can be displayed on the Mobiscroll calendar separately or mixed with other calendars and events.

Customizing the interaction, event workflows, whether the events are read-only is up to you. In this live demo, newly created events will land in the `primary calendar` of the authenticated user.

This example uses the [**integration plugin**](https://mobiscroll.com/docs/vue/eventcalendar/calendar-integrations) to connect to and load events from Outlook Calendars and it is not available in the trial. Give the live demo a try and see how it can be configured.

## What this demo shows

- Shows a monthly agenda view with events listed and grouped by date.
- **Header navigation** The month and year label in the top left opens date navigation. The previous and next arrows and the Today button on the right move between months and jump back to the current date.
- **Signed-out state** No events are loaded by default. The `Sync My Outlook Calendars` button in the header prompts the user to sign in to Outlook Calendar.
- **Authentication flow** Clicking the Outlook sign-in button opens a new browser tab where the user can authenticate with their Outlook account.
- **Signed-in state** After sign-in, the header button changes to `My Calendars`.
- **Calendar list** The `My Calendars` popup shows multiple calendar switches, such as the user's personal calendar and country-specific holiday calendars.
- **Sign-out** The bottom of the popup includes a `Log out of my account` button for disconnecting the Outlook Calendar account.
- **Agenda list** The area below the header lists events for the selected month. Events in the agenda are grouped by date.
- **Sticky day headers** As the agenda scrolls vertically, each day stays visible with a sticky date header while its events appear underneath.
- **Events** Events appear as agenda cards with a colored strip on the left and the event title next to it. Start and end times are stacked on the right side of timed events. All-day events show the all-day label on the right.
- **Event interaction** Hovering an event highlights it.
- **Event selection** Clicking an event selects and highlights it.

## Best for

- **Personal calendar scheduling** Building agenda views where users can browse and manage events from their own Outlook Calendar.
- **Calendar visibility controls** Letting users toggle individual Outlook calendars on or off inside the same agenda view.
- **Readonly calendar handling** Displaying calendars without write access while preventing edits to those calendar rows.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-themeVariant) - option
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`exclusiveEndDates`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-exclusiveEndDates) - option
- [`selectedDate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-selectedDate) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`page-loading`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onPageLoading) - event
- [`selected-date-change`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onSelectedDateChange) - event
- [`display`](https://mobiscroll.com/docs/vue/popup/api#opt-display) - option
- [`anchor`](https://mobiscroll.com/docs/vue/popup/api#opt-anchor) - option
- [`isOpen`](https://mobiscroll.com/docs/vue/popup/api#opt-isOpen) - option
- [`scrollLock`](https://mobiscroll.com/docs/vue/popup/api#opt-scrollLock) - option
- [`showOverlay`](https://mobiscroll.com/docs/vue/popup/api#opt-showOverlay) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/popup/api#opt-touchUi) - option
- [`width`](https://mobiscroll.com/docs/vue/popup/api#opt-width) - option
- [`close`](https://mobiscroll.com/docs/vue/popup/api#event-onClose) - event
- [`cssClass`](https://mobiscroll.com/docs/vue/forms/button#opt-cssClass) - option
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import { outlookCalendarSync } from '@mobiscroll/calendar-integration'
import {
  MbscButton,
  MbscCalendarNav,
  MbscCalendarNext,
  MbscCalendarPrev,
  MbscEventcalendar,
  MbscPopup,
  MbscSwitch,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const calendarData = ref({})
const calendarIds = ref([])
const isLoggedIn = ref(false)
const isLoading = ref(false)
const isPopupOpen = ref(false)
const isToastOpen = ref(false)
const myAnchor = ref()
const myCalendars = ref([])
const myEvents = ref([])
const mySelectedDate = ref(new Date())
const myView = { agenda: { type: 'month' } }
const toastMessage = ref('')

const buttonRef = ref()
const startDate = ref()
const endDate = ref()
const timer = ref()

function handleError(resp) {
  toastMessage.value = resp.error ? resp.error : resp.result.error.message
  isToastOpen.value = true
}

function handlePageLoading(args) {
  clearTimeout(timer.value)
  startDate.value = args.viewStart
  endDate.value = args.viewEnd
  timer.value = setTimeout(() => {
    if (outlookCalendarSync.isSignedIn()) {
      isLoading.value = true
      outlookCalendarSync
        .getEvents(calendarIds.value, startDate.value, endDate.value)
        .then((events) => {
          myEvents.value = events
          isLoading.value = false
        })
        .catch(handleError)
    }
  }, 200)
}

function toggleCalendar(ev, calendarId) {
  const checked = ev.target.checked
  calendarData.value[calendarId].checked = checked
  if (checked) {
    isLoading.value = true
    calendarIds.value = [...calendarIds.value, calendarId]
    outlookCalendarSync
      .getEvents([calendarId], startDate.value, endDate.value)
      .then((events) => {
        isLoading.value = false
        myEvents.value = [...myEvents.value, ...events]
      })
      .catch(handleError)
  } else {
    calendarIds.value = calendarIds.value.filter((id) => id !== calendarId)
    myEvents.value = myEvents.value.filter((item) => item.outlookCalendarId !== calendarId)
  }
}

function openPopup() {
  myAnchor.value = buttonRef.value.instance.nativeElement
  isPopupOpen.value = true
}

function navigate() {
  mySelectedDate.value = new Date()
}

function signIn() {
  if (!outlookCalendarSync.isSignedIn()) {
    outlookCalendarSync.signIn().catch(handleError)
  }
}

function signOut() {
  outlookCalendarSync.signOut().catch(handleError)
}

onMounted(() => {
  function onSignedIn() {
    isLoggedIn.value = true
    outlookCalendarSync
      .getCalendars()
      .then((calendars) => {
        const newCalendarIds = []
        const newCalendarData = {}

        calendars.sort((c) => (c.isDefaultCalendar ? -1 : 1))

        for (const c of calendars) {
          newCalendarIds.push(c.id)
          newCalendarData[c.id] = { checked: true }
        }

        calendarIds.value = newCalendarIds
        calendarData.value = newCalendarData
        myCalendars.value = calendars
        isLoading.value = true

        return outlookCalendarSync.getEvents(newCalendarIds, startDate.value, endDate.value)
      })
      .then((events) => {
        myEvents.value = events
        isLoading.value = false
      })
      .catch(handleError)
  }

  const onSignedOut = () => {
    calendarIds.value = []
    calendarData.value = {}
    isLoggedIn.value = false
    isPopupOpen.value = false
    myCalendars.value = []
    myEvents.value = []
  }

  // Init Outlook Client
  outlookCalendarSync.init({
    redirectUri: '<YOUR_OUTLOOK_REDIRECT_URI>',
    clientId: '<YOUR_OUTLOOK_CLIENT_ID>',
    onSignedIn: onSignedIn,
    onSignedOut: onSignedOut
  })
})
</script>

<template>
  <MbscEventcalendar
    :data="myEvents"
    :exclusiveEndDates="true"
    :selectedDate="mySelectedDate"
    :view="myView"
    @page-loading="handlePageLoading"
    @selected-date-change="mySelectedDate = $event.date"
  >
    <template #header>
      <MbscCalendarNav />
      <div :class="'mds-loader' + (isLoading ? ' mds-loader-visible' : '')"></div>
      <div class="mbsc-flex mbsc-flex-1-0 mbsc-justify-content-end">
        <MbscButton v-if="isLoggedIn" ref="buttonRef" @click="openPopup"> My Calendars </MbscButton>
        <MbscButton v-if="!isLoggedIn" @click="signIn">Sync my outlook calendars</MbscButton>
        <MbscButton @click="navigate">Today</MbscButton>
      </div>
      <MbscCalendarPrev />
      <MbscCalendarNext />
    </template>
  </MbscEventcalendar>
  <MbscPopup
    display="anchored"
    :anchor="myAnchor"
    :contentPadding="false"
    :isOpen="isPopupOpen"
    :scrollLock="false"
    :showOverlay="false"
    :touchUi="false"
    :width="400"
    @close="isPopupOpen = false"
  >
    <div class="mbsc-form-group-inset">
      <div class="mbsc-form-group-title">My Calendars</div>
      <MbscSwitch
        v-for="cal in myCalendars"
        :key="cal.id"
        :label="cal.name"
        v-model="calendarData[cal.id].checked"
        @change="toggleCalendar($event, cal.id)"
      />
    </div>
    <div class="mbsc-form-group-inset">
      <MbscButton cssClass="mbsc-button-block" @click="signOut">Log out of my account</MbscButton>
    </div>
  </MbscPopup>
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="isToastOpen = false" />
</template>

<style>
.mds-loader {
  width: 32px;
  height: 32px;
  border: 4px solid #8c8c8c;
  border-bottom-color: transparent;
  border-radius: 50%;
  display: inline-block;
  box-sizing: border-box;
  animation: mds-loader-rotation 1s linear infinite;
  visibility: hidden;
}

.mds-loader-visible {
  visibility: visible;
}

@keyframes mds-loader-rotation {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import { outlookCalendarSync } from '@mobiscroll/calendar-integration'
import {
  MbscButton,
  MbscCalendarNav,
  MbscCalendarNext,
  MbscCalendarPrev,
  MbscEventcalendar,
  MbscPopup,
  MbscSwitch,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscDateType,
  MbscEventcalendarView,
  MbscPageLoadingEvent
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const calendarData = ref<{ [key: string]: { checked: boolean } }>({})
const calendarIds = ref<string[]>([])
const isLoggedIn = ref<boolean>(false)
const isLoading = ref<boolean>(false)
const isPopupOpen = ref<boolean>(false)
const isToastOpen = ref<boolean>(false)
const myAnchor = ref<HTMLElement>()
const myCalendars = ref<{ name: string; id: string }[]>([])
const myEvents = ref<MbscCalendarEvent[]>([])
const mySelectedDate = ref<MbscDateType>(new Date())
const myView: MbscEventcalendarView = { agenda: { type: 'month' } }
const toastMessage = ref<string>('')

const buttonRef = ref<typeof MbscButton>()
const startDate = ref<Date>()
const endDate = ref<Date>()
const timer = ref<ReturnType<typeof setTimeout>>()

function handleError(resp: { error?: string; result: { error: { message: string } } }) {
  toastMessage.value = resp.error ? resp.error : resp.result.error.message
  isToastOpen.value = true
}

function handlePageLoading(args: MbscPageLoadingEvent) {
  clearTimeout(timer.value)
  startDate.value = args.viewStart
  endDate.value = args.viewEnd
  timer.value = setTimeout(() => {
    if (outlookCalendarSync.isSignedIn()) {
      isLoading.value = true
      outlookCalendarSync
        .getEvents(calendarIds.value, startDate.value!, endDate.value!)
        .then((events: MbscCalendarEvent[]) => {
          myEvents.value = events
          isLoading.value = false
        })
        .catch(handleError)
    }
  }, 200)
}

function toggleCalendar(ev: Event, calendarId: string) {
  const checked = (ev.target as HTMLInputElement).checked
  calendarData.value[calendarId].checked = checked
  if (checked) {
    isLoading.value = true
    calendarIds.value = [...calendarIds.value, calendarId]
    outlookCalendarSync
      .getEvents([calendarId], startDate.value!, endDate.value!)
      .then((events) => {
        isLoading.value = false
        myEvents.value = [...myEvents.value, ...events]
      })
      .catch(handleError)
  } else {
    calendarIds.value = calendarIds.value.filter((id) => id !== calendarId)
    myEvents.value = myEvents.value.filter((item) => item.outlookCalendarId !== calendarId)
  }
}

function openPopup() {
  myAnchor.value = buttonRef.value!.instance.nativeElement
  isPopupOpen.value = true
}

function navigate() {
  mySelectedDate.value = new Date()
}

function signIn() {
  if (!outlookCalendarSync.isSignedIn()) {
    outlookCalendarSync.signIn().catch(handleError)
  }
}

function signOut() {
  outlookCalendarSync.signOut().catch(handleError)
}

onMounted(() => {
  function onSignedIn() {
    isLoggedIn.value = true
    outlookCalendarSync
      .getCalendars()
      .then((calendars: { name: string; id: string; isDefaultCalendar: boolean }[]) => {
        const newCalendarIds = []
        const newCalendarData: { [key: string]: { checked: boolean } } = {}

        calendars.sort((c) => (c.isDefaultCalendar ? -1 : 1))

        for (const c of calendars) {
          newCalendarIds.push(c.id)
          newCalendarData[c.id] = { checked: true }
        }

        calendarIds.value = newCalendarIds
        calendarData.value = newCalendarData
        myCalendars.value = calendars
        isLoading.value = true

        return outlookCalendarSync.getEvents(newCalendarIds, startDate.value!, endDate.value!)
      })
      .then((events: MbscCalendarEvent[]) => {
        myEvents.value = events
        isLoading.value = false
      })
      .catch(handleError)
  }

  const onSignedOut = () => {
    calendarIds.value = []
    calendarData.value = {}
    isLoggedIn.value = false
    isPopupOpen.value = false
    myCalendars.value = []
    myEvents.value = []
  }

  // Init Outlook Client
  outlookCalendarSync.init({
    redirectUri: '<YOUR_OUTLOOK_REDIRECT_URI>',
    clientId: '<YOUR_OUTLOOK_CLIENT_ID>',
    onSignedIn: onSignedIn,
    onSignedOut: onSignedOut
  })
})
</script>

<template>
  <MbscEventcalendar
    :data="myEvents"
    :exclusiveEndDates="true"
    :selectedDate="mySelectedDate"
    :view="myView"
    @page-loading="handlePageLoading"
    @selected-date-change="mySelectedDate = $event.date"
  >
    <template #header>
      <MbscCalendarNav />
      <div :class="'mds-loader' + (isLoading ? ' mds-loader-visible' : '')"></div>
      <div class="mbsc-flex mbsc-flex-1-0 mbsc-justify-content-end">
        <MbscButton v-if="isLoggedIn" ref="buttonRef" @click="openPopup"> My Calendars </MbscButton>
        <MbscButton v-if="!isLoggedIn" @click="signIn">Sync my outlook calendars</MbscButton>
        <MbscButton @click="navigate">Today</MbscButton>
      </div>
      <MbscCalendarPrev />
      <MbscCalendarNext />
    </template>
  </MbscEventcalendar>
  <MbscPopup
    display="anchored"
    :anchor="myAnchor"
    :contentPadding="false"
    :isOpen="isPopupOpen"
    :scrollLock="false"
    :showOverlay="false"
    :touchUi="false"
    :width="400"
    @close="isPopupOpen = false"
  >
    <div class="mbsc-form-group-inset">
      <div class="mbsc-form-group-title">My Calendars</div>
      <MbscSwitch
        v-for="cal in myCalendars"
        :key="cal.id"
        :label="cal.name"
        v-model="calendarData[cal.id].checked"
        @change="toggleCalendar($event, cal.id)"
      />
    </div>
    <div class="mbsc-form-group-inset">
      <MbscButton cssClass="mbsc-button-block" @click="signOut">Log out of my account</MbscButton>
    </div>
  </MbscPopup>
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="isToastOpen = false" />
</template>

<style>
.mds-loader {
  width: 32px;
  height: 32px;
  border: 4px solid #8c8c8c;
  border-bottom-color: transparent;
  border-radius: 50%;
  display: inline-block;
  box-sizing: border-box;
  animation: mds-loader-rotation 1s linear infinite;
  visibility: hidden;
}

.mds-loader-visible {
  visibility: visible;
}

@keyframes mds-loader-rotation {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
</style>
```

## Implementation notes

- This demo is structurally identical to `sync-events-google-calendar` with the following Outlook-specific differences:
- Install `@mobiscroll/calendar-integration`. Import `outlookCalendarSync`. Call `outlookCalendarSync.init({ clientId: '<YOUR_OUTLOOK_CLIENT_ID>', redirectUri: '<YOUR_OUTLOOK_REDIRECT_URI>', onSignedIn, onSignedOut })` at component init (no `apiKey`). Use `view: { agenda: { type: 'month' } }` with `exclusiveEndDates: true`.
- In `onSignedIn`: call `outlookCalendarSync.getCalendars()`. Calendars have a `name` property (not `summary`) and sort by `isDefaultCalendar` (not `primary`). Initialize `calendarIds` with all IDs, then call `outlookCalendarSync.getEvents(calendarIds, startDate, endDate)`.
- Toggling a calendar **on**: fetch only that single calendar's events - `outlookCalendarSync.getEvents([calendarId], startDate, endDate)` - and **append** the result to the existing events array. Toggling **off**: filter the events array by `event.outlookCalendarId !== calendarId` (no re-fetch needed).
- `onPageLoading`, custom header (sign-in button → "My Calendars" button after login), loading spinner, `Popup` with `Switch` list and "Sign out" button, `Toast` for errors, and "Today" button are all identical to `sync-events-google-calendar`.

## Run it in your project

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/vue/getting-started/installation

## Related demos

- Previous: [Google Calendar Sync](https://demo.mobiscroll.com/vue/agenda/sync-events-google-calendar.md) - Load and sync Google Calendar events in a Vue agenda view — authenticate users, toggle calendars on/off, and create events using the plugin.
- Next: [Load Public Google Calendar Events](https://demo.mobiscroll.com/vue/agenda/load-events-from-google-calendar.md) - Load events from a public Google Calendar into the Vue agenda view using the integration plugin — connect with a Calendar ID and API key.
