Vanilla JavaScript

FullCalendar can be used without a framework. You create a Calendar object, hand it a DOM element and an object of options, and call its methods directly. The Vue, Angular and Web Component packages are wrappers around this same API. The React and Preact packages are not: they are FullCalendar written in those libraries, accepting the same options.

This package is released under an MIT license. Useful links:

This guide does not go into depth about setting up a build system. Please consult the aforementioned example projects for that.

Installation#

There are three ways to get FullCalendar onto the page. Whichever you use, you’ll need to choose one of the stock themes (see them live), along with a color palette. The examples below show a simplified way of importing the color palette (ex: purple.css), but please be aware, there are better ways to integrate with dark mode.

Via NPM#

For non-trivial projects, it is recommended to use a build system like Vite or Rollup. It compiles everything into a unified bundle, so you won’t need to manage <script> tags yourself.

Install the FullCalendar-related dependencies:

npm install --save fullcalendar temporal-polyfill

The temporal-polyfill package is small and tree-shakable, and can be used as a global polyfill if you explicitly opt-in (more info).

FullCalendar’s functionality is broken up into “plugins” (see a full list). A plugin you don’t import is never compiled into your bundle. Import the ones you need and supply them to a new Calendar instance:

import { Calendar } from 'fullcalendar';
import themePlugin from 'fullcalendar/themes/monarch'; // YOUR THEME
import dayGridPlugin from 'fullcalendar/daygrid';
import timeGridPlugin from 'fullcalendar/timegrid';
import listPlugin from 'fullcalendar/list';

// stylesheets
import 'fullcalendar/skeleton.css'; // ALWAYS NEED SKELETON
import 'fullcalendar/themes/monarch/theme.css'; // YOUR THEME
import 'fullcalendar/themes/monarch/palettes/purple.css'; // YOUR THEME'S PALETTE

const calendarEl = document.getElementById('calendar');
const calendar = new Calendar(calendarEl, {
  plugins: [themePlugin, dayGridPlugin, timeGridPlugin, listPlugin],
  initialView: 'dayGridMonth',
  headerToolbar: {
    left: 'prev,next today',
    center: 'title',
    right: 'dayGridMonth,timeGridWeek,listWeek'
  }
});
calendar.render();

You must initialize your calendar with at least one plugin that provides a view!

Example repos:

Via Script Tags#

It’s possible to include <script> tags in the head of your HTML page and initialize a calendar via browser globals, using one of FullCalendar’s prebuilt bundles. Obtain the standard fullcalendar bundle in one of the following ways:

Then, write the following initialization code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <!-- STANDARD JS -->
    <script src="https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/all/global.js"></script>

    <!-- THEME JS -->
    <script src="https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/global.js"></script>

    <!-- STYLESHEETS -->
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/skeleton.css' rel='stylesheet' />
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/theme.css' rel='stylesheet' />
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/palettes/purple.css' rel='stylesheet' />

    <script>
      document.addEventListener("DOMContentLoaded", function () {
        const calendarEl = document.getElementById("calendar");
        const calendar = new FullCalendar.Calendar(calendarEl, {
          initialView: "dayGridMonth",
        });
        calendar.render();
      });
    </script>
  </head>
  <body>
    <div id="calendar"></div>
  </body>
</html>

View a runnable example →

With globals there is no plugins array to write. The bundle’s all/global(.min).js file registers the following plugins for you:

The supplemental plugins are not in the bundle: Google Calendar, iCalendar, RRule, Moment formatting and Luxon formatting. Each ships its own global.js, which registers itself when loaded after all/global.js. Some also need their third-party library loaded first. See each plugin’s page for its script tags.

Via Script Tags (ESM)#

FullCalendar can also be used as an ES module within a <script type="module"> tag, with import maps resolving the package names. Unlike the globals technique, you import and supply each plugin yourself:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />

    <!-- STYLESHEETS -->
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/skeleton.css' rel='stylesheet' />
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/theme.css' rel='stylesheet' />
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/palettes/purple.css' rel='stylesheet' />

    <script type="importmap">
      {
        "imports": {
          "preact": "https://cdn.jsdelivr.net/npm/preact@10.29.1/dist/preact.mjs",
          "preact/hooks": "https://cdn.jsdelivr.net/npm/preact@10.29.1/hooks/dist/hooks.mjs",
          "preact/jsx-runtime": "https://cdn.jsdelivr.net/npm/preact@10.29.1/jsx-runtime/dist/jsxRuntime.mjs",
          "preact/compat": "https://cdn.jsdelivr.net/npm/preact@10.29.1/compat/dist/compat.mjs",
          "preact/compat/client": "https://cdn.jsdelivr.net/npm/preact@10.29.1/compat/client.mjs",
          "temporal-polyfill/fns/ZonedDateTime": "https://cdn.jsdelivr.net/npm/temporal-polyfill@1.0.1/fns/ZonedDateTime.js",
          "temporal-polyfill/fns/PlainDateTime": "https://cdn.jsdelivr.net/npm/temporal-polyfill@1.0.1/fns/PlainDateTime.js",
          "temporal-polyfill/fns/Instant": "https://cdn.jsdelivr.net/npm/temporal-polyfill@1.0.1/fns/Instant.js",
          "@full-ui/headless-calendar": "https://cdn.jsdelivr.net/npm/@full-ui/headless-calendar@7.1.0/index.js",
          "fullcalendar": "https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/index.js",
          "fullcalendar/themes/monarch": "https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/index.js",
          "fullcalendar/interaction": "https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/interaction.js",
          "fullcalendar/daygrid": "https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/daygrid.js",
          "fullcalendar/timegrid": "https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/timegrid.js",
          "fullcalendar/list": "https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/list.js"
        }
      }
    </script>
    <script type="module">
      import { Calendar } from "fullcalendar";
      import themePlugin from "fullcalendar/themes/monarch";
      import dayGridPlugin from "fullcalendar/daygrid";
      import timeGridPlugin from "fullcalendar/timegrid";
      import listPlugin from "fullcalendar/list";

      document.addEventListener("DOMContentLoaded", function () {
        const calendarEl = document.getElementById("calendar");
        const calendar = new Calendar(calendarEl, {
          plugins: [themePlugin, dayGridPlugin, timeGridPlugin, listPlugin],
          initialView: "dayGridMonth",
          headerToolbar: {
            left: "prev,next today",
            center: "title",
            right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
          },
        });
        calendar.render();
      });
    </script>
  </head>
  <body>
    <div id="calendar"></div>
  </body>
</html>

A supplemental plugin such as RRule works the same way: import it and add it to plugins. The import map needs an entry for the plugin’s package and for each module it imports, such as @fullcalendar/core/protected-api and the third-party library it wraps.

Options#

The second argument to new Calendar is an object of options. It accepts all of FullCalendar’s options. Example:

const calendar = new Calendar(calendarEl, {
  plugins: [dayGridPlugin],
  initialView: 'dayGridMonth',
  weekends: false,
  events: [
    { title: 'event 1', date: '2026-04-01' },
    { title: 'event 2', date: '2026-04-02' },
  ]
});

Options can also be scoped to a single view, or to a type of view, through the views option. See View-Specific Options.

Handlers#

A handler is an option whose value is a function that is called when something happens. For example, the dateClick handler is called whenever the user clicks on a date:

const calendar = new Calendar(calendarEl, {
  plugins: [dayGridPlugin, interactionPlugin],
  dateClick: function(info) {
    alert(info.dateStr);
  }
});

Handlers can also be attached and detached after initialization with the on and off methods. See Handlers.

Modifying Options#

An option can be read or changed after the calendar has been initialized:

calendar.getOption('weekends'); // false
calendar.setOption('weekends', true);

The calendar rerenders as needed. A few options can’t be set this way and have a dedicated method instead. See Get/Set Options Dynamically.

Content Injection#

There are many settings throughout the API for injecting custom content, like the eventContent event render hook. In vanilla JS, such a setting accepts content in the following formats.

Unescaped text, provided as a string:

eventContent: 'some text'

HTML, wrapped in an object:

eventContent: { html: '<i>some html</i>' }

An array of DOM nodes, wrapped in an object:

eventContent: { domNodes: arrayOfDomNodes }

A function that returns any of the above formats:

eventContent: function(info) {
  const italicEl = document.createElement('i')

  if (info.event.extendedProps.isUrgent) {
    italicEl.innerHTML = 'urgent event'
  } else {
    italicEl.innerHTML = 'normal event'
  }

  return { domNodes: [italicEl] }
}

Each setting is given different arguments. For example, eventContent receives a single object with a whole bunch of properties.

Virtual DOM#

FullCalendar internally uses a virtual DOM to do its rendering. This is normally behind the scenes, but content injection is one place in the API that allows you to use it. A function may return a virtual DOM node:

import { Calendar } from 'fullcalendar'
import { createElement } from 'fullcalendar/preact' // subpath of same package

const calendar = new Calendar(calendarEl, {
  eventContent: (info) => (
    createElement('i', null,
      info.event.extendedProps.isUrgent ?
        'urgent event' :
        'normal event'
    )
  )
})

Custom Toolbar Markup#

The CalendarController technique for writing your own toolbar markup is only available in the React, Preact and Vue connectors. In vanilla JS, arrange the built-in toolbar with headerToolbar and footerToolbar, and define your own buttons with buttons. For anything beyond that, write your own markup and call the date navigation methods from it.

Custom Views#

It’s possible to make calendar views that have custom rendering logic. See Custom Views via JS.

Calendar API#

In vanilla JS you already hold the Calendar object, so there is nothing to unwrap. Call methods on it directly:

calendar.next();

This calls the next method, which moves the calendar forward one month/week/day. Methods are documented throughout these docs alongside the features they belong to:

The calendar’s own lifecycle is controlled with methods too:

FullCalendar Premium#

How do you use FullCalendar Premium’s plugins in vanilla JS? They are no different than any other plugin. Whichever technique you use, make sure to include your schedulerLicenseKey.

Premium via NPM#

You’ll need an additional package:

npm install --save fullcalendar-scheduler

Then, initialize your calendar:

import { Calendar } from 'fullcalendar';
import themePlugin from 'fullcalendar/themes/monarch';
import resourceTimelinePlugin from 'fullcalendar-scheduler/resource-timeline';

// stylesheets
import 'fullcalendar/skeleton.css';
import 'fullcalendar/themes/monarch/theme.css';
import 'fullcalendar/themes/monarch/palettes/purple.css';

const calendarEl = document.getElementById('calendar');
const calendar = new Calendar(calendarEl, {
  schedulerLicenseKey: 'XXX',
  plugins: [themePlugin, resourceTimelinePlugin],
  initialView: 'resourceTimelineWeek',
  resources: [
    // your resource list
  ]
});
calendar.render();

Example repos:

Premium via Script Tags#

First, follow the standard script tag instructions above. Then obtain the premium fullcalendar-scheduler bundle in one of the following ways:

Then, write the following initialization code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />

    <!-- STANDARD + SCHEDULER JS -->
    <script src='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/all/global.js'></script>
    <script src='https://cdn.jsdelivr.net/npm/fullcalendar-scheduler@7.1.0/all/global.js'></script>

    <!-- THEME JS -->
    <script src='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/global.js'></script>

    <!-- STYLESHEETS -->
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/skeleton.css' rel='stylesheet' />
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/theme.css' rel='stylesheet' />
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@7.1.0/themes/monarch/palettes/purple.css' rel='stylesheet' />

    <script>
      document.addEventListener("DOMContentLoaded", function () {
        const calendarEl = document.getElementById("calendar");
        const calendar = new FullCalendar.Calendar(calendarEl, {
          schedulerLicenseKey: "XXX",
          initialView: "resourceTimelineWeek",
        });
        calendar.render();
      });
    </script>
  </head>
  <body>
    <div id="calendar"></div>
  </body>
</html>

View a runnable example →

The fullcalendar-scheduler bundle’s all/global(.min).js file includes the following plugins:

TypeScript#

Nothing special is needed for TypeScript integration. Types are included in the fullcalendar and fullcalendar-scheduler packages, so the NPM technique works as-is once your build system compiles TypeScript. Vite does this out of the box:

import { Calendar, CalendarOptions } from 'fullcalendar';
import themePlugin from 'fullcalendar/themes/monarch';
import dayGridPlugin from 'fullcalendar/daygrid';

const calendarEl: HTMLElement = document.getElementById('calendar')!;

const options: CalendarOptions = {
  plugins: [themePlugin, dayGridPlugin],
  initialView: 'dayGridMonth'
};

const calendar = new Calendar(calendarEl, options);
calendar.render();