V5-beta Release Notes and Upgrade Guide

Getting the Beta

You can install the packages via npm using the @5.0.0-beta.4 postfix:

npm install --save @fullcalendar/core@5.0.0-beta.4 @fullcalendar/daygrid@5.0.0-beta.4

Or, you can use one of the new pre-built bundles (see below)

Virtual DOM

FullCalendar now internally uses a miniature virtual-DOM library called Preact. Aside from making the codebase more maintainable, it makes FullCalendar more performant to end-users. DOM manipulations and page reflows are kept to a minimum. This is especially true for rerendering events. FullCalendar no longer needs to rerender ALL events when just one event changes. It rerenders only what it needs to (#3003).

Just because we use a virtual DOM doesn’t mean we no longer think about performance. We still care about limiting the amount of rerender execution, even though it performs fewer real DOM operations. This will continue to be a priority as we further develop the beta.

How does this affect FullCalendar’s API? It doesn’t really. From any of the content injection options like eventContent you are able to construct and return a virtual DOM node. Learn more in this article. Aside from that, you won’t need to think about the virtual DOM.

Real React

The @fullcalendar/react package is no longer merely a connector. It leverages the actual React virtual DOM engine the rest of your React app uses. It swaps out the Preact rendering engine it normally uses for real React, so you can take advantage of Fiber. This is sourcery that Adam will likely blog about in the future.

CSS and DOM Structure

Firstly, the DOM structure of the calendar has been simplified quite a bit. There is less nesting. Month view and daygrid view have particularly benefitted from this. Each row is represented by a single <tr> and events are rooted in individual <td> cells, whereas in v4 we had many tables-within-tables (addresses #2853)

The CSS has been completely rewritten. Most importantly, the selectors are flatter. Instead of a selector like .fc-event .fc-title we now use something like .fc-event-title. This results in fewer selectors battling for precedence and makes styling easier to override.

As a result, if you’ve written custom styling, it will most likely need to be rewritten for v5, or at the very least you will need to swap out your classNames. To help you do this, we will likely release a className-upgrade document prior to the official release.

CSS Importing

In v4, it was your responsibility to import all of fullcalendar’s stylesheets. You may have done this in one your project’s SASS files. Or, if you had a build system that handled CSS, you may have done this from your JavaScript.

In v5, you no longer need to do this! The plugins will import their own stylesheets. So, you’ll be able remove lines like these:

import { Calendar } from '@fullcalendar/core'
import timeGridPlugin from '@fulllcalendar/timegrid'

// DON'T DO THIS ANYMORE! it will happen automatically
// import '@fullcalendar/core/main.css';
// import '@fullcalendar/daygrid/main.css'; // a dependency of timegrid
// import '@fullcalendar/timegrid/main.css';

let calendar = new Calendar(calendarEl, {
  plugins: [ timeGridPlugin ]
  // other options...

HOWEVER, you’ll need a build system that is able to handle CSS. Some popular ones:

Configuring your build system to handle CSS is beyond the scope of this document. There are many examples out there.

If you don’t use a build system, meaning you use manual <script> tags and browser globals, then please read the next section…

Pre-built Bundles

What if you want to avoid using a build system? What if you prefer manual <script> tags and browser globals? This is why we are beginning to offer pre-built bundles of plugins (#4566). In fact, using the pre-built bundles will be the ONLY way to use manual <script> tags going forward. The individual plugins will no longer provide browser-runnable UMD files.

First, get the bundle distro files on the Getting Started page »

To use a bundle, do something like this:

<link ref='fullcalendar/main.css' rel='stylesheet' />
<script src='fullcalendar/main.js'></script>
  document.addEventListener('DOMContentLoaded', function() {
    var calendarEl = document.getElementById('calendar')
    var calendar = new FullCalendar.Calendar(calendarEl, {
      // plugins: [ 'dayGrid' ] // DON'T DO THIS ANYMORE!

You’ll still need to include the CSS file. You won’t need to define the plugins array anymore.

For initializing scheduler, do something like this:

<link ref='fullcalendar-scheduler/main.css' rel='stylesheet' />
<script src='fullcalendar-scheduler/main.js'></script><!-- only one JS file. don't include the other bundle -->
  document.addEventListener('DOMContentLoaded', function() {
    var calendarEl = document.getElementById('calendar')
    var calendar = new FullCalendar.Calendar(calendarEl, {
      // plugins: [ 'resourceTimeline' ] // DON'T DO THIS ANYMORE!

When using the scheduler bundle, you don’t need to include both the standard bundle AND the scheduler bundle. The scheduler bundle already INCLUDES the standard plugins.


  • header
  • footer

When specifying headerToolbar and footerToolbar, the { left, center, right } properties are still available to you. However, the following properties have been added to better support RTL locales:

  • start - if the calendar is left-to-right, it will appear on the left. if right-to-left, it will appear on the right.
  • end - if the calendar is left-to-right, it will appear on the right. if right-to-left, it will appear on the left.


  • defaultView
  • viewSkeletonRender
  • viewSkeletonDestroy

Use the view render hooks instead:

  • viewClassNames - for adding classNames to the root view element
  • viewDidMount - simply renamed from viewSkeletonRender
  • viewWillUnmount - simply renamed from viewSkeletonDestroy

See below for changes to how Custom JS Views are implemented.

Current Date

  • defaultDate

Date Rendering

The header elements above the day cells in daygrid and timegrid views. Also, the title elements for each day in list view. For the timeline view, jump down to slot rendering.

  • columnHeader
  • dayHeaders - simply renamed. Accepts true or false for enabling headers.
  • columnHeaderFormat
  • columnHeaderText
  • columnHeaderHtml

Use the day-header render hooks instead:

  • dayHeaderClassNames
  • dayHeaderContent - for emulating columnHeaderText, return a string. For emulating columnHeaderHtml, return an object like { html: '' }. The received arguments are different.
  • dayHeaderDidMount
  • dayHeaderWillUnmount

The days cells in daygrid and timegrid views:

  • dayRender

Use the day-cell render hooks instead:

  • dayCellClassNames - for injecting classNames
  • dayCellContent - for if you injected DOM content via dayRender
  • dayCellDidMount - for if you needed the DOM element in dayRender
  • dayCellWillUnmount
  • dayMinWidth - creates a horizontal scrollbar if day cells get any narrower. For daygrid and timegrid views only. Requires the @fullcalendar/scrollgrid premium plugin.

The horizontal time slots in timegrid view or the vertical datetime slots in timeline view. In timeline view, even though these slots might represent distinct days, they are still considered "slots" as opposed to "day cells".

  • minTime
  • maxTime
  • slotWidth
  • slotMinWidth - simply renamed. same exact behavior. only applies to timeline view

The following slot render hooks are now available:

  • slotLabelClassNames
  • slotLabelContent
  • slotLabelDidMount
  • slotLabelWillUnmount

You can now customize the long span of content next to the slot's date/time text. In timegrid view, this is the horizontal space that passes under all of the days. In timeline view, this is the vertical space that passes through the resources. Use the following slot render hooks:

  • slotLaneClassNames
  • slotLaneContent
  • slotLaneDidMount
  • slotLaneWillUnmount
timeline slot classNames

In timeline view, slots now have more descriptive classNames like fc-slot-future, fc-slot-past, fc-slot-fri, fc-slot-sat, fc-slot-today, etc.

Date rendering, in aggregate:

  • datesRender
  • datesDidUpdate - renamed from datesRender. However, you are encouraged to use the render hooks on the individual date cells instead, like the day header, day cell, and slot render hooks.
  • datesDestroy

No direct replacement. Instead, handle individual date cells via dayHeaderWillUnmount, dayCellWillUnmount, or slotLabelWillUnmount.

Week numbers:

  • weekNumbersWithinDays

This is now the default behavior. The weekNumbersWithinDays:false behavior has been retired. See the old docs for an illustration of the difference.

  • weekLabel
  • weekText - simply renamed. the text that gets prefixed to the formatted week number

default week number formatting

Week numbers were previously formatted as plain numeric values like "6". Now, by default, they are formatted with their weekText prefix, so they'll look like "W6". To change back to the old behavior, change weekNumberFormat to { week: 'numeric' }

The following week-number render hooks are now available:

  • weekNumberClassNames
  • weekNumberContent
  • weekNumberDidMount
  • weekNumberWillUnmount

The area where the “all-day” text is displayed, both in timegrid view and list view:

  • allDayText
  • allDayHtml

Use the all-day render hooks instead:

  • allDayClassNames
  • allDayContent - for emulating allDayText, assign a string. For emulating allDayHtml, assign an object like { html: '' }
  • allDayDidMount
  • allDayWillUnmount

Event Rendering

  • eventRender
  • eventDestroy

Use the new event render hooks instead:

  • eventClassNames - for injecting classNames
  • eventContent - for if you injected DOM content via eventRender. You cannot cancel rendering by returning false however. Instead, attached a display:'none' property on the Event Input.
  • eventDidMount - for if you needed the DOM element in eventRender
  • eventWillUnmount - like eventDestroy, but receives additional arguments
  • eventPositioned

No direct replacement. You can use eventDidMount to know when the element has been inserted into the DOM, but it's no longer possible to know when its position has stabilized.

  • _eventsPositioned

No direct replacement.

  • Calendar::rerenderEvents method

Call Calendar::render after initialization to rerender everything.

  • timeGridEventMinHeight

Set a min-height on your event elements via CSS. The computed min-height is considered when positioning events.

  • rendering setting within each Event Object
  • eventRendering setting on the Calendar

These settings have been renamed to display and eventDisplay respectively. They accept:

  • 'block' - new. always display as a solid rectangle in daygrid
  • 'list-item' - new. always display with a dot when in daygrid
  • 'auto' - new. the default. in daygrid, will display as 'block' if all-day or multi-day, otherwise will display as 'list-item'
  • 'background'
  • 'inverse-background'
  • 'none' - new. don't display at all

daygrid events

list-item event-display example By default, single-day timed events in daygrid will render with a dot as opposed to a solid filled rectangle. To revert to the old behavior, set the calendar-wide eventDisplay option to 'block'. See above for other choices.

background events

Background events now display their title (addresses #2746). They previously did not. To prevent this, don’t assign a title to the Event Object Input. Alternatively, you can override the rendering like this:

eventContent: function(arg) {
  if (arg.event.rendering.match(/background/)) { // handles inverse-background too
    return null

event classNames

Event elements now have more descriptive classNames about what dates they span. For example, fc-event-past, fc-event-future, and fc-event-today.

More Events Popover

When there are too many events to fit within a single day:

  • eventLimit
  • dayMaxEventRows - the max number of stacked event levels within a given day. This includes the +more link if present. This is the same behavior as v4's eventLimit. Just as in v4, setting it to true uses the cell's natural dimensions to limit events.
  • dayMaxEvents - the max number of events within a given day, not counting the +more link (addresses #3035)
  • eventLimitClick
  • moreLinkClick - renamed from eventLimitClick. No longer receives the moreEl or dayEl properties. The segs property has been rename to allSegs.
  • eventLimitText

Use the more link render hooks instead:

  • moreLinkClassNames
  • moreLinkContent - for emulating eventLimitText, return a string. The received arguments are different.
  • moreLinkDidMount
  • moreLinkWillUnmount

Resource Rendering

  • resourceText
  • resourceRender

A resource "label" is anywhere the name of a resource is displayed. They exist in the header of vertical resource view and the side section of resource timeline view.

Use the following resource render hooks going forward:

  • resourceLabelClassNames - for injecting classNames
  • resourceLabelContent - for if you injected DOM content via resourceRender. For emulating resourceText, return a string. The received arguments are different.
  • resourceLabelDidMount - for if you needed the DOM element in resourceRender
  • resourceLabelWillUnmount

A resource "lane" is an element in resource-timeline view. It runs horizontally across the timeline slots for each resource.

The following resource render hooks are now available:

  • resourceLaneClassNames
  • resourceLaneContent
  • resourceLaneDidMount
  • resourceLaneWillUnmount
  • Calendar::rerenderResources method

Call Calendar::render after initialization to rerender everything.

When resources are grouped together in resource-timeline view:

  • resourceGroupText

A resource group "label" is where a group's name is displayed.

Use the following resource group render hooks going forward:

  • resourceGroupLabelClassNames
  • resourceGroupLabelContent - you can emulate resourceGroupText by returning a string. The received arguments are different.
  • resourceGroupLabelDidMount
  • resourceGroupLabelWillUnmount

A resource group "lane" is the horizontal area running along the time slots.

The following resource group render hooks are now available:

  • resourceGroupLaneClassNames
  • resourceGroupLaneContent
  • resourceGroupLaneDidMount
  • resourceGroupLaneWillUnmount

The area on the side of resource-timeline view that contains resource names and data.

  • resourceLabelText

The "resource-area header" is above the resource data and displays the text "Resources" by default. It was previously called the "resource label", a term which is now being used to describe something else! When resourceAreaColumns is activated, it will not be displayed.

Use the resource-area header render hooks going forward:

  • resourceAreaHeaderClassNames
  • resourceAreaHeaderContent - for emulating resourceLabelText, assign a string
  • resourceAreaHeaderDidMount
  • resourceAreaHeaderWillUnmount
  • resourceColumns

Properties within resourceColumns:

  • labelText

Renamed to these properties within resourceAreaColumns:

  • headerClassNames
  • headerContent - for emulating labelText, assign a string.
  • headerDidMount
  • headerWillUnmount

Properties within resourceColumns:

  • text
  • render

Renamed to these properties within resourceAreaColumns:

  • cellClassNames - for injecting classNames
  • cellContent - for if you injected DOM content via render. For emulating text, return a string. The received arguments have changed.
  • cellDidMount - for if you needed the DOM element in render
  • cellWillUnmount

List View Rendering

In list view, the “No events to display” message.

  • listDayAltFormat
  • noEventsMessage

The following render hooks are now available:

  • noEventsClassNames
  • noEventsContent - for emulating noEventsMessage, assign a string
  • noEventsDidMount
  • noEventsWillUnmount

Now Indicator Rendering

The following render hooks are now available for customizing the now indicator:

  • nowIndicatorClassNames
  • nowIndicatorContent
  • nowIndicatorDidMount
  • nowIndicatorWillUnmount

Calendar Sizing


No longer accepts a function. Reassign imperatively via setOption.


No longer accepts a function. Reassign imperatively via setOption.

No longer accepts the 'parent' value. Instead, assign '100%' (addresses #4650). Any other valid css values are accepted as well.

New settings related to stickiness:

Event Sources

  • allDayDefault


  • dir

Custom JS Views

Custom views written as JavaScript classes will need to be refactored to work. Subclasses of View are no longer accepted. Instead, you specify a plain configuration object.

In the old way, you had different methods that were called when different pieces of data changed:

class CustomView extends View { // a class. this is the OLD way

  renderSkeleton() {
    this.el.innerHTML =
      '<div class="view-title"></div>' +
      '<div class="view-events"></div>'

  renderDates(dateProfile) {
    this.el.querySelector('.view-title').innerHTML = dateProfile.currentRange.start.toUTCString()

  renderEvents(eventStore) {
    this.el.querySelector('.view-events').innerHTML = Object.keys(eventStore).length + ' events'


Now, the content function gets called when ANY change occurs:

const CustomViewConfig = { // a plain object. this is the NEW way

  classNames: [ 'custom-view' ],

  content: function(props) {
    let html =
      '<div class="view-title">' +
        props.dateProfile.currentRange.start.toUTCString() +
      '</div>' +
      '<div class="view-events">' +
        Object.keys(props.eventStore).length + ' events' +

    return { html: html }


You can return any of the available content injection formats such as HTML, real DOM nodes, or virtual DOM nodes.

If you want to maintain state across calls to content, you are better off writing a Preact/React component instead. More information »

Interaction Plugin

The @fullcalendar/interaction plugin’s browser globals have changed:

new FullCalendarInteraction.Draggable(settings) // the OLD way

new FullCalendar.Draggable(settings) // the NEW way

Moment and Luxon Plugins

The Moment plugin’s browser globals have changed:

// OLD

// NEW

The Moment plugin’s ES6 exports have changed:

// OLD
import { toDuration } from '@fullcalendar/moment'

// NEW
import { toMomentDuration } from '@fullcalendar/moment'

The Luxon plugin’s ES6 exports have changed:

// OLD
import { toDateTime, toDuration } from '@fullcalendar/luxon'

// NEW
import { toLuxonDateTime, toLuxonDuration } from '@fullcalendar/luxon'

Other Misc Changes

  • feature: you can force rerendering of anything on the calendar by calling the Calendar::render method again after initialization
  • feature: full sourcemaps included for each NPM package (#4719)
  • fix: timeline event drag/resize when on second line, pops to top (#4893)
  • fix: timeline scrolling sometimes gets out of sync when using a scroll wheel (#4889)
  • fix: rerenderDelay causes selectable and editable lag (#4770)
  • fix: CSP doesn’t allow setting of inline CSS (#4317)
  • fix: when eventSourceSuccess callback throws error, looks like JSON parsing failed (#4947)
  • fix: always show more-link when supplying 0 (#2978)

React Connector

When using the React connector, you can now return virtual DOM nodes to customize rendering (react#12):

import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'

export const DemoApp(props) => (
  <div className='wrapper'>
      eventContent={(arg) => (
        <div class='custom-event-content'>

Vue Connector

Previouly, when using the Vue connector, you specified each option as its own attribute in your template:

<!-- the old way -->

Now, you specify them as a single root options object:

<!-- the new way -->

Of course you’ll need to create this object somewhere:

const AppComponent = {
  data: function() {
    return {
      calendarOptions: {
        weekends: false,
        dateClick: this.handleDateClick
  methods: {
    handleDateClick: function(arg) {
      alert('clicked on ' + arg.dateStr)

This results in less duplication between your Vue component’s JS and template (vue#47). It also thankfully blurs the distinction between props and “handlers” for which you’d need to use v-on or @. All properties are treated equally, resuling in a simpler API.

You now have the ability to customize rendering with the use of Vue scoped slots (addresses vue#14):

<FullCalendar :options='calendarOptions'>
  <template v-slot:eventContent='arg'>
    <b>{{ arg.timeText }}</b>
    <i>{{ arg.event.title }}</i>

This is possible with any of the *Content options in the API.


  • fix: Class instances in extendedProps are converted to plain objects (vue#53)

Angular Connector

Firstly, the Angular connector now requires Angular 9.

In v4, when using the Angular connector, you specified each option as its own attribute in your template:

<!-- the old way -->

Now, you specify them as a single root options object:

<!-- the new way -->

Of course you’ll need to create this object somewhere:

class AppComponent {

  calendarOptions = {
    weekends: false,
    dateClick: this.handleDateClick.bind(this) // binding is important!

  handleDateClick(arg) {
    alert('clicked on ' + arg.dateStr)


This results in less duplication between your Angular component’s JS and template. It also thankfully blurs the distinction between props and “handlers” for which you’d need to use (parentheses) instead of [brackets]. All properties are treated equally, resuling in a simpler API.

Upgrading from V3

Many developers will be upgrading from v3 instead of v4. We will likely release a separate guide for this process before the official v5 is released. In the meantime, here are some tips for upgrading from v3 -> v5 in lieu of a full guide:

  1. Follow the v3 -> v4 upgrade guide but ignore the following areas:
    • “Initialization” and anything related to <script> tags or stylesheets
    • anything related to content injection, such as options with the words render, text, or html in them
  2. Learn how to install and initialize a v5 calendar from the Getting Started article.
  3. Follow this v4 -> v5 upgrade guide afterwards.