---
title: "Vue Scheduler Public Google Calendar Integration Example | Mobiscroll"
component: "Scheduler"
framework: "Vue"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/vue/scheduler/load-events-from-google-calendar"
---

# Public Google Calendar Integration - Vue Scheduler demo

Load public Google Calendar events into the Vue Scheduler — connect with your Calendar ID and API key via the integration plugin. For Vue and Nuxt.

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

## Description

Event data can be loaded from remote sources, like public google calendars. Through the [**integration plugin**](https://mobiscroll.com/docs/vue/eventcalendar/calendar-integrations) you can easily show events that are available in a public calendar. The integration plugin needs to be added to the download package or installed separately from a dedicated NPM package.

You will just need the `CALENDAR_ID` and an `apiKey` from google and you should be ready to roll.

Public calendars are read-only. If you are interested in syncing private google calendars with read/write/delete access, [check out this example](https://demo.mobiscroll.com/vue/scheduler/sync-events-google-calendar).

This example uses the [**integration plugin**](https://mobiscroll.com/docs/vue/eventcalendar/calendar-integrations) to connect to and load events from Google Calendar 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

- A desktop weekly scheduler layout with a fixed week strip at the top, a fixed all-day row below it, and a scrollable scheduler time grid for the selected week.
- **Custom header** The current month and year are shown on the left side of the header.
- **View switching** A segmented control in the center lets users switch between `Month`, `Week`, `Day`, and `Agenda` views, with `Week` selected by default.
- **Secondary navigation** Blue previous and next arrow buttons are shown on the right side of the header with a `Today` button between them.
- **Week view** The fixed strip below the header displays the days of the selected week, with the current date highlighted by a blue circle.
- **All-day events** All-day events appear in a dedicated row that stays fixed above the time grid.
- **Time grid** The scheduler grid scrolls vertically through the hours of the selected week.
- **Timed events** Timed events are displayed in the weekly scheduler grid as light blue cards with a blue stripe on the left, the event title in bold, and the exact start and end time displayed above the title.
- **Event overlapping** If events overlap, the scheduler places them side by side without hiding any of them or causing other conflicts.
- **Current time** A blue current-time line appears across the time grid, with a small blue dot marking the current date.
- **Hover behavior** Hovering over the time grid shows a time indicator that follows the cursor in 15-minute increments.
- **Event selection** Clicking an event selects and highlights it.
- **Event creation** Double-clicking the time grid or clicking and dragging on it creates a new event.
- **Monthly calendar view** Selecting the `Month` option from the segmented control switches the layout to a monthly calendar view. This shows a desktop month view event calendar with a full monthly grid and event labels inside the day cells.
- **Daily scheduler view** Selecting the `Day` option from the segmented control switches the layout to a daily scheduler view. This shows the days of the week at the top, an all-day section for all-day events, and a 24-hour time grid for one day for timed events.
- **Weekly agenda view** Selecting the `Agenda` option from the segmented control switches the layout to a weekly agenda view. This shows the days of the week at the top, and below the events listed in weekly agenda.

## Best for

- **Google Calendar overlays** Showing events from public Google Calendars inside a Mobiscroll scheduler.
- **Read-only event publishing** Sharing team, organization, or community calendars where visitors need to browse events but not edit them.

## 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
- [`onInit`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onInit) - event
- [`labels`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-labels) - option
- [`exclusiveEndDates`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-exclusiveEndDates) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`page-loading`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onPageLoading) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import { googleCalendarSync } from '@mobiscroll/calendar-integration'
import {
  MbscCalendarNav,
  MbscCalendarNext,
  MbscCalendarPrev,
  MbscCalendarToday,
  MbscEventcalendar,
  MbscSegmented,
  MbscSegmentedGroup,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

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

const CALENDAR_ID = 'theacidmedia.net_8l6v679q5j2f7q8lpmcjr4mm3k@group.calendar.google.com'

const myEvents = ref([])
const isLoading = ref(false)
const firstDay = ref(null)
const lastDay = ref(null)
const toastMessage = ref('')
const isToastOpen = ref(false)
const calendarView = ref('week')
const myView = ref({
  scheduler: { type: 'week' }
})

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

function loadEvents() {
  isLoading.value = true
  googleCalendarSync
    .getEvents(CALENDAR_ID, firstDay.value, lastDay.value)
    .then((resp) => {
      isLoading.value = false
      myEvents.value = resp
    })
    .catch(onError)
}

function handleToastClose() {
  isToastOpen.value = false
}

function handlePageLoading(args) {
  const start = args.viewStart
  const end = args.viewEnd

  // Calculate dates
  // (pre-load events for previous and next pages as well)
  if (calendarView.value === 'month') {
    firstDay.value = start
    lastDay.value = end
  } else {
    firstDay.value = new Date(start.getFullYear(), start.getMonth(), start.getDate() - 7)
    lastDay.value = new Date(end.getFullYear(), end.getMonth(), end.getDate() + 7)
  }

  loadEvents()
}

onMounted(() => {
  // Init google client
  googleCalendarSync.init({
    apiKey: '<YOUR_GOOGLE_API_KEY>',
    onInit: loadEvents
  })
})

function handleViewChange() {
  switch (calendarView.value) {
    case 'month':
      myView.value = {
        calendar: { labels: true }
      }
      break
    case 'week':
      myView.value = {
        scheduler: { type: 'week' }
      }
      break
    case 'day':
      myView.value = {
        scheduler: { type: 'day' }
      }
      break
    case 'agenda':
    default:
      myView.value = {
        calendar: { type: 'week' },
        agenda: { type: 'week' }
      }
      break
  }
}
</script>

<template>
  <MbscEventcalendar
    className="md-google-calendar "
    :class="{ 'md-loading-events': isLoading }"
    :exclusiveEndDates="true"
    :view="myView"
    :data="myEvents"
    @page-loading="handlePageLoading"
  >
    <template #header>
      <MbscCalendarNav className="google-cal-header-nav" />
      <div class="md-spinner">
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
      </div>
      <div class="google-cal-header-picker">
        <MbscSegmentedGroup v-model="calendarView" @change="handleViewChange">
          <MbscSegmented value="month"> Month </MbscSegmented>
          <MbscSegmented value="week"> Week </MbscSegmented>
          <MbscSegmented value="day"> Day </MbscSegmented>
          <MbscSegmented value="agenda"> Agenda </MbscSegmented>
        </MbscSegmentedGroup>
      </div>
      <MbscCalendarPrev className="google-cal-header-prev" />
      <MbscCalendarToday className="google-cal-header-today" />
      <MbscCalendarNext className="google-cal-header-next" />
    </template>
  </MbscEventcalendar>
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
</template>

<style>
.md-google-calendar .mbsc-segmented {
  max-width: 300px;
  margin: 0 auto;
}

.google-cal-header-picker {
  flex: 1 0 auto;
}

/* material header order */

.mbsc-material.google-cal-header-prev {
  order: 1;
}

.mbsc-material.google-cal-header-next {
  order: 2;
}

.mbsc-material.google-cal-header-nav {
  order: 3;
}

.mbsc-material .google-cal-header-picker {
  order: 4;
}

.mbsc-material .google-cal-header-today {
  order: 5;
}

/* windows header order */

.mbsc-windows.google-cal-header-nav {
  order: 1;
}

.mbsc-windows.google-cal-header-prev {
  order: 2;
}

.mbsc-windows.google-cal-header-next {
  order: 3;
}

.mbsc-windows .google-cal-header-picker {
  order: 4;
}

.mbsc-windows .google-cal-header-today {
  order: 5;
}

/* loading spinner and overlay */

.md-loading-events .md-sync-events-overlay {
  position: absolute;
  z-index: 2;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

.md-spinner {
  visibility: hidden;
  position: relative;
  width: 20px;
  height: 20px;
}

.md-loading-events .md-spinner {
  visibility: visible;
}

.md-spinner .md-spinner-blade {
  position: absolute;
  left: 44.5%;
  top: 37%;
  width: 10%;
  height: 25%;
  border-radius: 50%/20%;
  background-color: #8c8c8c;
  -webkit-animation: md-spinner-fade 1s linear infinite;
  animation: md-spinner-fade 1s linear infinite;
  -webkit-animation-play-state: paused;
  animation-play-state: paused;
}

.md-spinner .md-spinner-blade:nth-child(1) {
  -webkit-animation-delay: -1.66667s;
  animation-delay: -1.66667s;
  -webkit-transform: rotate(30deg) translate(0, -150%);
  transform: rotate(30deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(2) {
  -webkit-animation-delay: -1.58333s;
  animation-delay: -1.58333s;
  -webkit-transform: rotate(60deg) translate(0, -150%);
  transform: rotate(60deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(3) {
  -webkit-animation-delay: -1.5s;
  animation-delay: -1.5s;
  -webkit-transform: rotate(90deg) translate(0, -150%);
  transform: rotate(90deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(4) {
  -webkit-animation-delay: -1.41667s;
  animation-delay: -1.41667s;
  -webkit-transform: rotate(120deg) translate(0, -150%);
  transform: rotate(120deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(5) {
  -webkit-animation-delay: -1.33333s;
  animation-delay: -1.33333s;
  -webkit-transform: rotate(150deg) translate(0, -150%);
  transform: rotate(150deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(6) {
  -webkit-animation-delay: -1.25s;
  animation-delay: -1.25s;
  -webkit-transform: rotate(180deg) translate(0, -150%);
  transform: rotate(180deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(7) {
  -webkit-animation-delay: -1.16667s;
  animation-delay: -1.16667s;
  -webkit-transform: rotate(210deg) translate(0, -150%);
  transform: rotate(210deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(8) {
  -webkit-animation-delay: -1.08333s;
  animation-delay: -1.08333s;
  -webkit-transform: rotate(240deg) translate(0, -150%);
  transform: rotate(240deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(9) {
  -webkit-animation-delay: -1s;
  animation-delay: -1s;
  -webkit-transform: rotate(270deg) translate(0, -150%);
  transform: rotate(270deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(10) {
  -webkit-animation-delay: -0.91667s;
  animation-delay: -0.91667s;
  -webkit-transform: rotate(300deg) translate(0, -150%);
  transform: rotate(300deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(11) {
  -webkit-animation-delay: -0.83333s;
  animation-delay: -0.83333s;
  -webkit-transform: rotate(330deg) translate(0, -150%);
  transform: rotate(330deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(12) {
  -webkit-animation-delay: -0.75s;
  animation-delay: -0.75s;
  -webkit-transform: rotate(360deg) translate(0, -150%);
  transform: rotate(360deg) translate(0, -150%);
}

.md-loading-events .md-spinner-blade {
  -webkit-animation-play-state: running;
  animation-play-state: running;
}

@-webkit-keyframes md-spinner-fade {
  0% {
    opacity: 0.85;
  }
  50% {
    opacity: 0.25;
  }
  100% {
    opacity: 0.25;
  }
}

@keyframes md-spinner-fade {
  0% {
    opacity: 0.85;
  }
  50% {
    opacity: 0.25;
  }
  100% {
    opacity: 0.25;
  }
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import { googleCalendarSync } from '@mobiscroll/calendar-integration'
import {
  MbscCalendarNav,
  MbscCalendarNext,
  MbscCalendarPrev,
  MbscCalendarToday,
  MbscEventcalendar,
  MbscSegmented,
  MbscSegmentedGroup,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscPageLoadingEvent
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

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

const CALENDAR_ID = 'theacidmedia.net_8l6v679q5j2f7q8lpmcjr4mm3k@group.calendar.google.com'

const myEvents = ref<MbscCalendarEvent[]>([])
const isLoading = ref<boolean>(false)
const firstDay = ref<any>(null)
const lastDay = ref<any>(null)
const toastMessage = ref<string>('')
const isToastOpen = ref<boolean>(false)
const calendarView = ref<string>('week')
const myView = ref<MbscEventcalendarView>({
  scheduler: { type: 'week' }
})

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

function loadEvents() {
  isLoading.value = true
  googleCalendarSync
    .getEvents(CALENDAR_ID, firstDay.value, lastDay.value)
    .then((resp: MbscCalendarEvent[]) => {
      isLoading.value = false
      myEvents.value = resp
    })
    .catch(onError)
}

function handleToastClose() {
  isToastOpen.value = false
}

function handlePageLoading(args: MbscPageLoadingEvent) {
  const start = args.viewStart!
  const end = args.viewEnd!

  // Calculate dates
  // (pre-load events for previous and next pages as well)
  if (calendarView.value === 'month') {
    firstDay.value = start
    lastDay.value = end
  } else {
    firstDay.value = new Date(start.getFullYear(), start.getMonth(), start.getDate() - 7)
    lastDay.value = new Date(end.getFullYear(), end.getMonth(), end.getDate() + 7)
  }

  loadEvents()
}

onMounted(() => {
  // Init google client
  googleCalendarSync.init({
    apiKey: '<YOUR_GOOGLE_API_KEY>',
    onInit: loadEvents
  })
})

function handleViewChange() {
  switch (calendarView.value) {
    case 'month':
      myView.value = {
        calendar: { labels: true }
      }
      break
    case 'week':
      myView.value = {
        scheduler: { type: 'week' }
      }
      break
    case 'day':
      myView.value = {
        scheduler: { type: 'day' }
      }
      break
    case 'agenda':
    default:
      myView.value = {
        calendar: { type: 'week' },
        agenda: { type: 'week' }
      }
      break
  }
}
</script>

<template>
  <MbscEventcalendar
    className="md-google-calendar "
    :class="{ 'md-loading-events': isLoading }"
    :exclusiveEndDates="true"
    :view="myView"
    :data="myEvents"
    @page-loading="handlePageLoading"
  >
    <template #header>
      <MbscCalendarNav className="google-cal-header-nav" />
      <div class="md-spinner">
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
        <div class="md-spinner-blade"></div>
      </div>
      <div class="google-cal-header-picker">
        <MbscSegmentedGroup v-model="calendarView" @change="handleViewChange">
          <MbscSegmented value="month"> Month </MbscSegmented>
          <MbscSegmented value="week"> Week </MbscSegmented>
          <MbscSegmented value="day"> Day </MbscSegmented>
          <MbscSegmented value="agenda"> Agenda </MbscSegmented>
        </MbscSegmentedGroup>
      </div>
      <MbscCalendarPrev className="google-cal-header-prev" />
      <MbscCalendarToday className="google-cal-header-today" />
      <MbscCalendarNext className="google-cal-header-next" />
    </template>
  </MbscEventcalendar>
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
</template>

<style>
.md-google-calendar .mbsc-segmented {
  max-width: 300px;
  margin: 0 auto;
}

.google-cal-header-picker {
  flex: 1 0 auto;
}

/* material header order */

.mbsc-material.google-cal-header-prev {
  order: 1;
}

.mbsc-material.google-cal-header-next {
  order: 2;
}

.mbsc-material.google-cal-header-nav {
  order: 3;
}

.mbsc-material .google-cal-header-picker {
  order: 4;
}

.mbsc-material .google-cal-header-today {
  order: 5;
}

/* windows header order */

.mbsc-windows.google-cal-header-nav {
  order: 1;
}

.mbsc-windows.google-cal-header-prev {
  order: 2;
}

.mbsc-windows.google-cal-header-next {
  order: 3;
}

.mbsc-windows .google-cal-header-picker {
  order: 4;
}

.mbsc-windows .google-cal-header-today {
  order: 5;
}

/* loading spinner and overlay */

.md-loading-events .md-sync-events-overlay {
  position: absolute;
  z-index: 2;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

.md-spinner {
  visibility: hidden;
  position: relative;
  width: 20px;
  height: 20px;
}

.md-loading-events .md-spinner {
  visibility: visible;
}

.md-spinner .md-spinner-blade {
  position: absolute;
  left: 44.5%;
  top: 37%;
  width: 10%;
  height: 25%;
  border-radius: 50%/20%;
  background-color: #8c8c8c;
  -webkit-animation: md-spinner-fade 1s linear infinite;
  animation: md-spinner-fade 1s linear infinite;
  -webkit-animation-play-state: paused;
  animation-play-state: paused;
}

.md-spinner .md-spinner-blade:nth-child(1) {
  -webkit-animation-delay: -1.66667s;
  animation-delay: -1.66667s;
  -webkit-transform: rotate(30deg) translate(0, -150%);
  transform: rotate(30deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(2) {
  -webkit-animation-delay: -1.58333s;
  animation-delay: -1.58333s;
  -webkit-transform: rotate(60deg) translate(0, -150%);
  transform: rotate(60deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(3) {
  -webkit-animation-delay: -1.5s;
  animation-delay: -1.5s;
  -webkit-transform: rotate(90deg) translate(0, -150%);
  transform: rotate(90deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(4) {
  -webkit-animation-delay: -1.41667s;
  animation-delay: -1.41667s;
  -webkit-transform: rotate(120deg) translate(0, -150%);
  transform: rotate(120deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(5) {
  -webkit-animation-delay: -1.33333s;
  animation-delay: -1.33333s;
  -webkit-transform: rotate(150deg) translate(0, -150%);
  transform: rotate(150deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(6) {
  -webkit-animation-delay: -1.25s;
  animation-delay: -1.25s;
  -webkit-transform: rotate(180deg) translate(0, -150%);
  transform: rotate(180deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(7) {
  -webkit-animation-delay: -1.16667s;
  animation-delay: -1.16667s;
  -webkit-transform: rotate(210deg) translate(0, -150%);
  transform: rotate(210deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(8) {
  -webkit-animation-delay: -1.08333s;
  animation-delay: -1.08333s;
  -webkit-transform: rotate(240deg) translate(0, -150%);
  transform: rotate(240deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(9) {
  -webkit-animation-delay: -1s;
  animation-delay: -1s;
  -webkit-transform: rotate(270deg) translate(0, -150%);
  transform: rotate(270deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(10) {
  -webkit-animation-delay: -0.91667s;
  animation-delay: -0.91667s;
  -webkit-transform: rotate(300deg) translate(0, -150%);
  transform: rotate(300deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(11) {
  -webkit-animation-delay: -0.83333s;
  animation-delay: -0.83333s;
  -webkit-transform: rotate(330deg) translate(0, -150%);
  transform: rotate(330deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(12) {
  -webkit-animation-delay: -0.75s;
  animation-delay: -0.75s;
  -webkit-transform: rotate(360deg) translate(0, -150%);
  transform: rotate(360deg) translate(0, -150%);
}

.md-loading-events .md-spinner-blade {
  -webkit-animation-play-state: running;
  animation-play-state: running;
}

@-webkit-keyframes md-spinner-fade {
  0% {
    opacity: 0.85;
  }
  50% {
    opacity: 0.25;
  }
  100% {
    opacity: 0.25;
  }
}

@keyframes md-spinner-fade {
  0% {
    opacity: 0.85;
  }
  50% {
    opacity: 0.25;
  }
  100% {
    opacity: 0.25;
  }
}
</style>
```

## Implementation notes

- Import `googleCalendarSync` from `@mobiscroll/calendar-integration` (a separately installed package). Define a single `CALENDAR_ID` constant - one public Google Calendar. No user sign-in required; access is via API key only. Set `exclusiveEndDates: true`. The calendar is read-only (no `clickToCreate`/`dragToCreate`).
- Default view: `scheduler: { type: 'week' }`. A `SegmentedGroup` view switcher in the header lets the user pick between four views:
  - `'month'` → `{ calendar: { labels: true } }`
  - `'week'` → `{ scheduler: { type: 'week' } }`
  - `'day'` → `{ scheduler: { type: 'day' } }`
  - `'agenda'` → `{ calendar: { type: 'week' }, agenda: { type: 'week' } }`
- Call `googleCalendarSync.init({ apiKey, onInit: loadEvents })` on mount. `onInit` fires when the integration is ready.
- **`loadEvents`**: calls `googleCalendarSync.getEvents(CALENDAR_ID, firstDay.current, lastDay.current)` and replaces the events array on success.
- **`onPageLoading`**: sets date refs and calls `loadEvents()`. For `'month'` view use `args.viewStart`/`args.viewEnd` directly. For all other views, pre-load ±7 days: `firstDay = start − 7 days`, `lastDay = end + 7 days`.
- **Custom header** (`renderHeader`, Angular: `headerTemplate`, Vue: `#header` slot): `CalendarNav` (left), spinner (12 blade divs, visible while loading), `SegmentedGroup` (Month/Week/Day/Agenda), then `CalendarPrev`, `CalendarToday`, `CalendarNext` (right).

## 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: [Outlook Calendar Integration](https://demo.mobiscroll.com/vue/scheduler/sync-events-outlook-calendar.md) - Connect Outlook Calendar to a Vue Scheduler — authenticate with Microsoft sign-in, load Outlook events, and sync add, edit, and delete actions.
