FullCalendarは、月・週・日・リスト表示とドラッグ&ドロップによる予定変更を備えた、JavaScriptのイベントカレンダーライブラリです。2026年6月にv7.0.0が出てから、9月5日に7.1.0、10月6日に7.1.1、10月12日に7.1.2と更新が続き、npm install fullcalendar で入るのはv7になりました。v7はパッケージ構成、CSSの読み込み方、見た目を変える設定の名前がまとめて変わったため、v6以前の解説記事のコードやCSSはそのままでは動きません。この記事では、v7.1.2を実際にnpmで入れて動かした結果をもとに、導入、日本語化、土日の色分けやテーマなどのカスタマイズ、データベース連携、無料で使える範囲と商用ライセンスを順に解説します。
まとめ:FullCalendar v7.1.2を使う前に押さえる要点
- 現行版は7.1.2(2026年10月12日)。本体は
fullcalendarパッケージ1つで、月表示などのプラグインはfullcalendar/daygridのようなサブパスから読み込みます - CSSは自動で注入されません。
skeleton.css・テーマのtheme.css・配色のパレットCSSの3枚を自分で読み込み、temporal-polyfillも一緒にインストールします - v7の描画結果はクラス名が短い記号になり、v6向けに書かれた
.fc-day-satのようなCSSは当たりません。土日の色分けはdayCellClassで自分のクラスを付けて行います - 標準機能はMITライセンスで商用利用も無料です。タイムライン表示やリソース表示はPremiumで、非公開の商用システムでは480ドルからの有償ライセンスが必要です
- v6の記事どおり
@fullcalendar/daygridを入れるとv6.1.21が入り、v7本体と混ざって動きません
FullCalendarの概要と現行版7.1.2の位置づけ
FullCalendarはAdam Shaw氏が開発するオープンソースのカレンダーUIで、公式サイトは fullcalendar.io、ソースはGitHubの fullcalendar/fullcalendar で公開されています。予約システム、会議室や設備の空き状況、シフト表、社内のスケジュール共有など、予定を日付の枠に並べてクリックやドラッグで操作する画面を短期間で作る用途で使われます。
v7はテーマの仕組みを作り直したメジャーバージョンで、Monarch・Forma・Breezy・Pulseの4つの新テーマと、v6までの見た目を引き継ぐClassicテーマが入りました。7.1系では細かな修正が続いています。7.1.1のリリースノートではMUI v9への対応と、React 19で inert 属性の警告が出る不具合の修正が挙げられています。7.1.2はアクセシビリティの修正が中心で、日セル内の予定がスクリーンリーダーで1件ずつ読み上げられるよう、リストの構造に改められました。
| 版 | 公開日 | 主な内容 |
|---|---|---|
| 7.1.2 | 2026-10-12 | npmのlatest。アクセシビリティの修正 |
| 7.1.1 | 2026-10-06 | MUI v9対応、React 19の警告修正 |
| 7.1.0 | 2026-09-05 | 月表示の描画量削減(破壊的変更あり) |
| 7.0.0 | 2026-06-19 | テーマ刷新、パッケージ統合 |
| 6.1.21 | 2026-06-18 | v6系の最新(10月14日時点) |
7.1.0の破壊的変更は、月表示で dayMaxEvents: true を使うと、表示に必要な予定だけをDOMへ挿入するようになった点です。その結果、eventContent などの描画フックが一部の予定にしか呼ばれなくなりました。描画フックで全件の集計をしているコードは、calendar.getEvents() から数える形に改めてください。
なお、WordPressの「WP FullCalendar」とObsidianの「Full Calendar」は、このライブラリを組み込んだ別の製品です。WP FullCalendarは説明文でFullCalendar 3.xを使うと明記しています。ObsidianのプラグインはFullCalendar 5.x系に依存しており、GitHubのリポジトリは2026年8月1日付でcommunity-archive配下のアーカイブ(読み取り専用)になりました。どちらにもこの記事のv7の手順は当てはまりません。
無料で使える範囲と商用利用のライセンス
標準機能とPremiumのライセンスの選び方
月・週・日・リスト・複数月の表示、ドラッグ&ドロップ、日本語化、テーマなど、ドキュメントで「Premium」と印の付いていない機能はすべてMITライセンスです。著作権表示とライセンス文を残せば、社内システムや受託開発、有料のSaaSを含めて無償で使えます。
有償になるのは、横軸に時間を取るタイムライン表示や、会議室・担当者などの「リソース」ごとに列や行を分ける表示です。これらはPremium(パッケージ名は fullcalendar-scheduler)に含まれ、公式のライセンスページが定める3つから選びます。
| 利用形態 | 選ぶライセンス | schedulerLicenseKey |
|---|---|---|
| 非公開・営利(一般的な業務システム) | 商用ライセンス(480ドルから) | 購入時に発行されるキー |
| 登録非営利団体の非商用利用・評価 | CC BY-NC-ND 4.0 | CC-Attribution-NonCommercial-NoDerivatives |
| ソース公開のプロジェクト | AGPLv3(v6まではGPLv3) | AGPL-My-Frontend-And-Backend-Are-Open-Source |
非商用枠の対象は登録された非営利団体だけで、政府機関や大学は含まれません。もう1つ見落としやすいのが、オープンソース向けの選択肢がGPLv3からAGPLv3に変わった点です。公式は、GPLv3のままだとソースを配布しないSaaSが無償で使えてしまう抜け道があったため、それを塞ぐ目的だと説明しています。AGPLv3はフロントエンドとバックエンドの両方をAGPLv3で公開できる場合に限られるので、非公開の業務システムでPremium機能を使うなら商用ライセンスを買うことになります。
商用ライセンスの価格表示は「Starting at $480」で、席数はPremiumのソース・JavaScript API・CSSに触れる開発者の人数で数えます。有効期限前に更新すれば基本価格の50%引き、期限後なら25%引きで、更新をやめても期限切れ前に出た最終版は使い続けられます。
Premiumのタイムライン表示を試す最小構成
評価だけなら、CC BY-NC-NDのキーを指定して期間の制限なく試せます(商用の本番環境では使えません)。Vanilla JSでは fullcalendar-scheduler を追加し、サブパスからリソースタイムラインのプラグインを読み込みます。
npm install [email protected] [email protected] temporal-polyfill
import { Calendar } from 'fullcalendar'
import resourceTimelinePlugin from 'fullcalendar-scheduler/resource-timeline'
import classicThemePlugin from 'fullcalendar/themes/classic'
import jaLocale from 'fullcalendar/locales/ja'
const calendar = new Calendar(document.getElementById('calendar'), {
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives', // 評価用
plugins: [classicThemePlugin, resourceTimelinePlugin],
locale: jaLocale,
timeZone: 'Asia/Tokyo',
initialView: 'resourceTimelineDay',
initialDate: '2026-10-05',
resources: [
{ id: 'a', title: '会議室A' },
{ id: 'b', title: '会議室B' },
],
events: [
{ title: '定例', start: '2026-10-05T10:00:00+09:00', end: '2026-10-05T11:00:00+09:00', resourceId: 'b' },
],
})
calendar.render()
このコードを7.1.2とjsdomで描画すると、会議室Aと会議室Bの行が並び、会議室Bの行に予定が表示されました。schedulerLicenseKey を消すと、カレンダーの下に「Your license key is invalid.」という表示が出ます。CSSは標準機能と同じ3枚で足ります。
v7のインストール手順:npmとCDN
npmで入れる手順(Vite・webpackなどのビルド環境)
本体と temporal-polyfill を一緒に入れます。temporal-polyfill はv7で全パッケージのpeerDependency(7.1.2では ^1.0.1)になりました。FullCalendarは内部で使うだけでグローバルには登録しないため、アプリ全体でTemporal APIを使いたい場合は import 'temporal-polyfill/global' を自分で書きます。なお、Deno向けのレジストリであるJSRには、2026年10月14日時点で @fullcalendar のスコープがありません。配布先はnpmだけです。
npm install [email protected] temporal-polyfill
テーマもプラグインとして読み込み、CSSは skeleton.css(必須)、テーマの theme.css、配色のパレットCSSの3枚を読み込みます。v6と同じ見た目にしたい場合は classic テーマを選びます。
import { Calendar } from 'fullcalendar'
import dayGridPlugin from 'fullcalendar/daygrid'
import classicThemePlugin from 'fullcalendar/themes/classic'
import 'fullcalendar/skeleton.css'
import 'fullcalendar/themes/classic/theme.css'
import 'fullcalendar/themes/classic/palette.css'
const calendar = new Calendar(document.getElementById('calendar'), {
plugins: [classicThemePlugin, dayGridPlugin],
initialView: 'dayGridMonth',
events: [{ title: '定例会議', start: '2026-10-05T10:00:00' }],
})
calendar.render()
v7のパッケージはESMだけで配布され、CommonJS版はなくなりました。公式の移行ガイドはNode.js 22.12以降と23以降なら require('fullcalendar') がそのまま動くとしており、Node.js 20系でも20.19.0以降は require() で同期ESMを読み込めます。ただし、トップレベルawaitを含むモジュールやJest独自の読み込み処理には別の制約があるため、テスト環境のESM対応を確認してください。ESMとCommonJSの違いはNative ESMとCommonJSの違いを整理した記事で解説しています。
CDNのscriptタグで入れる手順
ビルド環境を使わない場合は、jsDelivrから読み込みます。v7ではファイル名が変わり、v6の index.global.min.js は all/global.js に、ロケールは locales/ja/global.js の形になりました。all/global.js には interaction・daygrid・timegrid・list・multimonth の各プラグインが入っています。以下のURLは7.1.2ですべて取得できることを確認済みです。
<script src="https://cdn.jsdelivr.net/npm/[email protected]/all/global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/themes/classic/global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/locales/ja/global.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/skeleton.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/themes/classic/theme.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/themes/classic/palette.css" rel="stylesheet">
<script>
document.addEventListener('DOMContentLoaded', function () {
var calendar = new FullCalendar.Calendar(document.getElementById('calendar'), {
initialView: 'dayGridMonth',
locale: 'ja',
})
calendar.render()
})
</script>
<div id="calendar"></div>
URLのバージョンは @7.1.2 のように固定してください。@7 や無指定にすると、10月のように1週間で2回リリースが出たときに、気づかないまま表示が変わります。
v6の記事のコードがv7で動かない原因
v6では本体が @fullcalendar/core、月表示が @fullcalendar/daygrid という別パッケージでした。v7ではこれらが fullcalendar のサブパスに統合されました。ところが @fullcalendar/daygrid などの旧プラグインはnpmに残っており、10月14日時点でもlatestは6.1.21を指したままです。@fullcalendar/core は7.1.2が出ていますが、v7では型定義用のパッケージになり、直接importすると「@fullcalendar/core should not be imported directly」と表示されて Calendar は取り出せません。そのため、v6の記事どおりに入れると、エラーにならずにv6とv7が混ざることがあります。
2026年9月27日に空のプロジェクトで試した結果は次のとおりです(npmのタグの状態は10月14日時点でも同じです)。
| 実行したコマンド・操作 | 結果 |
|---|---|
npm i @fullcalendar/react @fullcalendar/daygrid |
daygridは6.1.21、coreは2版が混在。警告なし |
| core 7系の導入後にdaygrid 6.1.21を追加 | ERESOLVE で停止 |
| v7本体にv6のdaygridプラグインを渡して描画 | 描画時にTypeErrorで停止 |
2つ目はdaygridが @fullcalendar/core@"~6.1.21" を要求するため、3つ目のエラーメッセージは TypeError: Class constructor DayTableView cannot be invoked without 'new' でした。最初のケースはインストールが通るぶん、原因に気づきにくいパターンです。v7に移るときは、package.jsonから @fullcalendar/core・@fullcalendar/daygrid・@fullcalendar/timegrid・@fullcalendar/list・@fullcalendar/interaction を消し、import文を fullcalendar/daygrid(Reactなら @fullcalendar/react/daygrid)に書き換えます。Premiumの @fullcalendar/resource-timeline なども同様に、Vanilla JSでは fullcalendar-scheduler/resource-timeline、Reactでは @fullcalendar/react-scheduler に移っています。
パッケージ以外で書き換えが必要になる主な項目は次のとおりです。どれも旧名のままではエラーにならず、効かないだけなので見落としやすい箇所です。
- CSS:v6は自動で注入されていたが、v7では
skeleton.cssとテーマのCSSを明示的に読み込む - イベントの色:
backgroundColor・borderColorはcolorに、textColorはcontrastColorに、classNames(配列)はclassName(空白区切りの文字列)に改名。カレンダー全体のeventBackgroundColorもeventColorに変わった - クラスを付ける設定:
dayCellClassNamesなどの〜ClassNamesはdayCellClassのような〜Classに改名。7.1.2で旧名を渡すと、コンソールに「FullCalendar: Unknown option `dayCellClassNames`.」と出るだけでクラスは付かない - タイムゾーン:
@fullcalendar/luxon3と@fullcalendar/moment-timezoneは廃止。名前付きタイムゾーンは本体だけで扱える - Vue 2:
@fullcalendar/vue(Vue 2用)はv7で提供終了。Vue 2のまま使うならv6に留まる
基本の使い方:ビュー切り替えとイベント表示
実務でよく使う設定をまとめると、次のようになります。月・週・リストの切り替えボタン、営業時間の網掛け、1日に入りきらない予定の「他 N 件」表示を入れた例です。先ほどのCSS3枚と、idがcalendarの描画先要素を用意した状態で実行します。
import { Calendar } from 'fullcalendar'
import dayGridPlugin from 'fullcalendar/daygrid'
import timeGridPlugin from 'fullcalendar/timegrid'
import listPlugin from 'fullcalendar/list'
import interactionPlugin from 'fullcalendar/interaction'
import classicThemePlugin from 'fullcalendar/themes/classic'
import jaLocale from 'fullcalendar/locales/ja'
const calendar = new Calendar(document.getElementById('calendar'), {
plugins: [classicThemePlugin, dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
locale: jaLocale,
timeZone: 'Asia/Tokyo',
initialView: 'dayGridMonth',
initialDate: '2026-10-01',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,listWeek',
},
businessHours: { daysOfWeek: [1, 2, 3, 4, 5], startTime: '09:00', endTime: '18:00' },
dayMaxEvents: true,
eventColor: '#2563eb',
events: '/api/events',
})
calendar.render()
initialView に指定できるのは、読み込んだプラグインが提供するビューだけです。dayGridMonth はdaygrid、timeGridWeek はtimegrid、listWeek はlistが必要で、年間表示の multiMonthYear を使うなら fullcalendar/multimonth を追加します。headerToolbar の right に並べたビュー名が、そのまま切り替えボタンになります。
日本語化とタイムゾーンの設定
日本語化(locale: ja)
npmで入れた場合は fullcalendar/locales/ja をimportして locale に渡し、CDNの場合は locales/ja/global.js を読み込んでから locale: 'ja' を指定します。7.1.2同梱の日本語ロケールでは、ボタンが「前」「次」「今日」「月」「週」「日」「予定リスト」、終日欄が「終日」、あふれた予定が「他 N 件」、予定が無いリストが「表示する予定はありません」になります。
日本語ロケールには週の開始曜日の指定が入っていないため、既定の日曜始まりになります。月曜始まりにしたい場合は firstDay: 1 を追加してください。jsdomで描画すると、曜日の見出しが月曜から並びました。
名前付きタイムゾーン(timeZone)
timeZone の既定値は 'local'(閲覧者のブラウザの時刻)です。v7では 'Asia/Tokyo' のようなIANAのタイムゾーン名を、追加プラグインなしで指定できます。v6では名前付きタイムゾーンにLuxonかmoment-timezoneの連携プラグインが必要でしたが、v7ではpeerDependencyの temporal-polyfill を内部で使って解決します。
注意点は、コールバックやAPIが返す日付がTemporalではなく従来のJavaScriptの Date のままであることです。公式ドキュメントも、返ってくる Date が実質的に扱えるのはローカルとUTCだけだとしています。Asia/Tokyo を指定していても getHours() は閲覧者のPCのタイムゾーンで値を返すため、サーバーに送る値は startStr(オフセット付きのISO 8601文字列)を使うのが安全です。
日本の祝日を表示する方法
FullCalendarには祝日のデータが入っていません。内閣府が公開している 国民の祝日CSV(1955年から翌年分までを収録・Shift_JIS)をサーバー側で読み込み、背景イベントとして返すのが手軽です。
{ "title": "スポーツの日", "start": "2026-10-12", "allDay": true, "display": "background", "color": "#fde2e2" }
display: 'background' を付けると、予定の下に色の帯として描画されます。翌年分は毎年2月の掲載後に取得し、収録年の末尾も確認してください。年初に取得するだけでは、翌年分の追加を取り逃がす可能性があります。
見た目のカスタマイズ:テーマ・色・土日の色分け
v7のカスタマイズは、テーマとパレットを選ぶ、CSS変数で色を差し替える、〜Class 系の設定で自分のクラスを付ける、の順で考えます。最初に知っておきたいのは、v7が描画する要素のクラス名が fc-classic-uqo のような短い記号になっている点です。7.1.2をjsdomで描画して全要素のクラスを調べたところ、v6の fc-day-sat・fc-daygrid-day・fc-event のような意味のあるクラスは1つも出力されず、残っていた手がかりは日セルの data-date 属性だけでした。v6向けの記事にある「.fc-day-sat に背景色を付ける」CSSは、v7では何にも当たりません。
テーマとパレットの選び方
テーマは5種類で、それぞれに配色のパレットがあります。公式のテーマ一覧によると、内訳は次のとおりです。全テーマがダークモードに対応し、themes.fullcalendar.io で見比べられます。
| テーマ | デザインの元 | パレット(太字が既定) |
|---|---|---|
| Monarch | Material Design 3 | blue・green・purple・red・yellow |
| Forma | Fluent UI | blue・green・purple・red |
| Breezy | Tailwind Plus | amber・emerald・indigo・rose |
| Pulse | Apple風の簡素なデザイン | blue・green・purple・red |
| Classic | v6までの見た目 | 1種類のみ |
パレットCSSのパスはテーマによって違います。Classicだけが themes/classic/palette.css の1枚で、ほかの4テーマは themes/monarch/palettes/blue.css のように色名のファイルを選びます。既存のv6画面に近づけるならClassic、Material Design 3に合わせるならMonarch、Fluent UIに合わせるならFormaが候補です。配色の変更は、ほかの標準テーマでもパレットCSSで行えます。shadcn・MUI・Bootstrap 5を使っている画面では、それぞれのライブラリのテーマから見た目を取る専用の連携が用意されています。
CSS変数によるパレット色の上書き
パレットの一部の色だけを変えたいときは、パレットCSSの後に自分のCSSを読み込み、:root でCSS変数を上書きします。パレットはそのまま使うので、本体の更新で入る修正も受け取れます。Classicの palette.css には、主色の --fc-classic-primary(既定 #3788d8)、今日のセルの --fc-classic-today、現在時刻の線の --fc-classic-now、枠線の --fc-classic-border など27個の変数が定義されています。
/* my-calendar.css(palette.cssの後に読み込む) */
:root {
--fc-classic-primary: #0f766e;
--fc-classic-today: #fef3c7;
}
@media not print {
[data-color-scheme=dark] {
--fc-classic-today: #78350f66;
}
}
ダークモードは、親要素に data-color-scheme="dark" を付けるだけで切り替わります。ダークモードをクラス名や独自のメディアクエリで切り替えている場合は、この上書きが効かないため、公式の案内どおりパレットCSSを自分のプロジェクトへ複製して書き換えてください。予定ごとの色は eventColor やイベントの color で指定し、描画時に --fc-event-color として要素へ渡されます。
土日・祝日セルの色分け(dayCellClass)
土日の色分けは、dayCellClass(日セル)と dayHeaderClass(曜日の見出し)に関数を渡し、自分のクラスを返して行います。関数が受け取る情報には、7.1.2の型定義で曜日の番号 dow(0が日曜、6が土曜)が含まれています。
const weekendClass = (info) => (info.dow === 0 ? 'is-sun' : info.dow === 6 ? 'is-sat' : '')
const calendar = new Calendar(calendarEl, {
// plugins・locale などは前の例と同じ
dayCellClass: weekendClass,
dayHeaderClass: weekendClass,
})
/* 自分で付けたクラスなら、v7でもCSSで指定できる */
.is-sat { background: #eff6ff; color: #1d4ed8; }
.is-sun { background: #fef2f2; color: #b91c1c; }
2026年10月の月表示(月曜始まり)で描画すると、土曜と日曜の列にそれぞれクラスが付きました。同じ設定を dayCellClassNames という旧名で渡したときは、クラスは1つも付かず、警告だけが出ました。祝日も同じ関数の中で、サーバーから取った祝日の日付と照らし合わせてクラスを返せば色分けできます。週表示の時間帯の列には dayCellClass ではなく dayLaneClass を使います。
時間軸の範囲・間隔と高さの調整
週・日表示の時間軸は、slotMinTime(既定 00:00:00)、slotMaxTime(既定 24:00:00・その時刻を含まない)、slotDuration(既定 00:30:00)で決まります。業務時間だけを見せる設定は次のとおりです。
slotMinTime: '08:00:00',
slotMaxTime: '20:00:00',
slotDuration: '00:30:00',
nowIndicator: true, // 現在時刻の線を出す(既定はfalse)
height: 'auto', // スクロールせず全体を表示する
この設定で週表示を描画すると、時刻の目盛りは「8時」から「19時」まで並びました。slotMaxTime は終端を含まないため、20時の目盛りは出ません。height は未指定なら aspectRatio から計算され、'auto' を指定するとスクロールなしで全体が表示されます。
予定の時刻表記は eventTimeFormat で変えられます。{ hour: '2-digit', minute: '2-digit', hour12: false } を指定すると、月表示では「09:00」になりました。ただし、同じ指定のまま7.1.2の週表示をjsdomで描画した結果は「9時00分~10時30分」のままでした。公式ドキュメントは、既定値はビューごとに違うものの、1つの設定で全ビューに効くと説明しています。今回の結果はこの説明と食い違い、views で週表示(timeGrid)だけに同じ書式を指定しても変わりませんでした。原因は確定できていないため、週表示の書式は実際のブラウザで確かめてください。
ツールチップとリンク遷移(eventDidMount・url)
マウスを乗せたときに補足を出すには、予定の要素がDOMに入った直後に呼ばれる eventDidMount を使います。公式デモはここでTooltip.jsを適用していますが、ブラウザ標準の title 属性でも足ります。
eventDidMount(info) {
info.el.setAttribute('title', info.event.extendedProps.memo ?? '')
},
events: [
{ title: '定例会議', start: '2026-10-05T09:00:00+09:00', extendedProps: { memo: '第3会議室' } },
{ title: '仕様書', start: '2026-10-07', url: 'https://example.com/spec' },
],
url を持つ予定は a 要素として描画され、jsdomでも href にURLが入っていました。クリックすると同じタブで遷移します。別タブで開きたい場合は、eventClick の中で info.jsEvent.preventDefault() を呼んでから window.open(info.event.url) を実行します。
クリック・ドラッグによるイベントの追加・編集・削除
日付のクリック(dateClick)、範囲選択(select)、予定のドラッグ移動(eventDrop)、長さの変更(eventResize)には fullcalendar/interaction が必要です。予定のクリック(eventClick)だけならinteractionなしで動きます。dateClick やドラッグが反応しない場合は、interactionプラグインの読み込みと、editable: true・selectable: true の指定を確認してください。CDNの all/global.js にはinteractionが含まれています。
const calendar = new Calendar(calendarEl, {
// plugins などは前の例と同じ
selectable: true, // 範囲選択を有効にする
editable: true, // ドラッグ移動と長さ変更を有効にする
select(info) {
const title = prompt('予定名')
if (title) {
calendar.addEvent({ title, start: info.startStr, end: info.endStr, allDay: info.allDay })
}
calendar.unselect()
},
eventClick(info) {
if (confirm(`「${info.event.title}」を削除しますか?`)) info.event.remove()
},
async eventDrop(info) {
if (!info.event.id) { // selectで追加しただけの未保存の予定はidがない
info.revert()
return
}
const res = await fetch(`/api/events/${info.event.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ start: info.event.startStr, end: info.event.endStr, allDay: info.event.allDay }),
}).catch(() => null)
if (!res || !res.ok) info.revert() // 保存に失敗したら元の位置へ戻す
},
})
addEvent や remove は画面上の予定を変えるだけで、サーバーには何も送りません。7.1.2でも、読み込み済みの3件に addEvent で1件足すと4件、その予定の remove() で3件に戻るだけでした。eventDrop の例のように、保存APIの呼び出しと失敗時の info.revert() を組にしておくと、画面とデータベースの食い違いを防げます。eventResize にも同じ revert() があります。
データベース連携:JSONフィードのstart・endと応答形式
events にURLを渡すと、FullCalendarは表示中の期間を start と end のGETパラメータに入れて取りに来ます。前後の月へ移動したときやビューを切り替えたときに、必要な範囲だけ再取得する仕組みです。7.1.2で timeZone: 'Asia/Tokyo' を指定し、2026年10月の月表示を開いたとき、サーバーに届いたクエリは次のとおりでした(URLデコード済み)。
/api/events?start=2026-09-27T00:00:00+09:00&end=2026-11-08T00:00:00+09:00&timeZone=Asia/Tokyo
この例は日曜始まり・6週表示のため、取得範囲は9月27日から11月8日までです。月曜始まりに変更した場合は、この範囲も変わります。end はその時刻を含まない排他的な終端です。サーバー側では、この範囲と少しでも重なる予定を返します。下のサーバーに12月の予定と9月25日から29日までの連泊予定をテストデータとして足して確認したところ、12月の予定は返らず、連泊予定は表示されました。
読み取り用のJSONフィードを、依存パッケージなしのNode.jsで書くと次のようになります。HTMLの配信と前章のPATCHによる保存は実装していないため、画面側の開発サーバーでこのAPIへのプロキシを設定し、保存APIは別に用意してください。
// server.mjs(Node.js 22以降・依存パッケージなし)
import http from 'node:http'
const events = [
{ id: '1', title: '定例会議', start: '2026-10-05T10:00:00+09:00', end: '2026-10-05T11:00:00+09:00' },
{ id: '2', title: '出張', start: '2026-10-14', end: '2026-10-17', allDay: true },
]
// 日付だけの値(終日予定)は日本時間の0時として扱う
const toMs = (v) => Date.parse(v.length === 10 ? `${v}T00:00:00+09:00` : v)
http.createServer((req, res) => {
const url = new URL(req.url, 'http://localhost')
if (url.pathname !== '/api/events') { res.writeHead(404).end(); return }
const from = Date.parse(url.searchParams.get('start'))
const to = Date.parse(url.searchParams.get('end')) // 排他的な終端
const hits = events.filter((e) => {
const start = toMs(e.start)
// end省略時はFullCalendarの既定の長さ(終日1日・時刻付き1時間)を補う
const allDay = e.allDay ?? e.start.length === 10
const end = e.end ? toMs(e.end) : start + (allDay ? 86400000 : 3600000)
return start < to && end > from
})
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(hits))
}).listen(3000)
SQLで書くなら条件は WHERE start_at < :end AND end_at > :start です。start_at >= :start だけで絞ると、前月から続く連泊の出張のように、範囲の開始より前に始まる予定が消えます。パラメータ名は startParam・endParam で変えられるので、既存APIの引数名に合わせることもできます。
この例では、表示用の title・start に加え、保存APIで予定を識別するための id を返します。end は終日予定なら翌日の日付(10月14日から16日までの3日間なら "end": "2026-10-17")を入れます。APIを別ドメインに置く場合はCORSの設定も必要で、手順はCORSの仕組みとサーバー設定例の記事にまとめています。
React・Vue・Angularで使う場合のパッケージ
公式のコネクタは、v7でそれぞれのフレームワーク用パッケージの中にプラグインとテーマを抱える形になりました。Vanilla JS用の fullcalendar を別に入れる必要があるのはAngularだけです。対応バージョンは7.1.2の各パッケージのpeerDependenciesで確認しました。
| フレームワーク | 標準パッケージ | 対応バージョン | Premium用 |
|---|---|---|---|
| React | @fullcalendar/react |
React 17〜19 | @fullcalendar/react-scheduler |
| Vue | @fullcalendar/vue3 |
Vue 3.0.11以降 | @fullcalendar/vue3-scheduler |
| Angular | @fullcalendar/angular + fullcalendar |
Angular 16〜22 | @fullcalendar/angular-scheduler |
| MUI | @fullcalendar/mui |
MUI v7・v9 | peerにreact-schedulerを含む |
Reactのコネクタは、v6までは内部でPreactを動かすラッパーでしたが、v7ではReactで実装し直され、SSRとStrictModeでも動くようになりました(Preact用には @fullcalendar/preact が新設)。Preactとの違いはPreactの仕組みとReactとの違いの記事で扱っています。Reactでの最小構成は次のとおりです。
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/react/daygrid'
import classicThemePlugin from '@fullcalendar/react/themes/classic'
import jaLocale from '@fullcalendar/react/locales/ja'
import '@fullcalendar/react/skeleton.css'
import '@fullcalendar/react/themes/classic/theme.css'
import '@fullcalendar/react/themes/classic/palette.css'
export default function Schedule() {
return (
<FullCalendar
plugins={[classicThemePlugin, dayGridPlugin]}
initialView="dayGridMonth"
locale={jaLocale}
events="/api/events"
/>
)
}
MUIで画面を組んでいる場合は、@fullcalendar/mui を使うとMUIのテーマから色と部品を取ります。7.1.1でMUI v9に対応しており、MUI側の構成はMaterial UIのv9系の導入とテーマ設定の記事が参考になります。Angularは angular.json の styles に3枚のCSSを登録し、テンプレートで <full-calendar [options]="calendarOptions"> を使います。Angular側のバージョン事情はAngular 22.1の変更点の記事にまとめています。
FullCalendarを選ばないほうがよい場面
予定を並べて操作するUIならFullCalendarは第一候補ですが、次の条件に当てはまるなら別の手段を検討してください。
- 日付を1つ選ばせるだけの入力欄:カレンダー表示は過剰です。
<input type="date">か日付ピッカーのライブラリで足ります - 非公開の業務システムでタイムラインやリソース別の表示が必須なのに予算化できない:Premiumの商用ライセンスが開発者の人数分かかります。標準機能の週表示をリソースごとに並べる設計に変えるか、他のライブラリと比べてください
- Vue 2のまま改修を続けるシステム:v7のVue 2対応はありません。v6.1.21に固定し、Vue 3への移行と同時にv7へ上げる計画にします
- v6向けのCSSを大量に書き込んだ画面:v7ではクラス名で要素を指定できなくなるため、CSSの書き直しが前提になります。見た目を作り込んだv6の画面ほど移行の工数が膨らみます
既存のv6を動かしているシステムを、機能追加の予定もないのにv7へ上げる必要はありません。CSS・色の設定名・パッケージ名の書き換えが一度に発生するため、画面改修のタイミングに合わせるのが現実的です。会議室や設備の予約、空き枠の管理までを含めた仕組みをまとめて作る場合は、予約管理システムの開発支援のように、カレンダー画面とデータベース・予約ルールを一体で設計する選択肢もあります。ライブラリのライセンスの考え方全般はOSSのライセンス種別と採用判断の記事で整理しています。
よくある質問
FullCalendarは無料で商用利用できますか?
Premiumと印の付いていない標準機能はMITライセンスで、商用のシステムでも無料で使えます。タイムライン表示やリソース表示などのPremium機能を非公開の商用システムで使う場合は、480ドルからの商用ライセンスが必要です。
FullCalendarの最新バージョンはいくつですか?
2026年10月14日時点の最新は7.1.2(2026年10月12日公開)です。npmで fullcalendar を入れるとv7が入ります。同日時点のv6系の最新は6.1.21(2026年6月18日公開)です。
FullCalendarを日本語化するにはどうすればよいですか?
npmなら import jaLocale from 'fullcalendar/locales/ja' を locale に渡し、CDNなら locales/ja/global.js を読み込んで locale: 'ja' を指定します。月曜始まりにしたい場合は firstDay: 1 を追加します。
土曜・日曜の色を変えるCSSが効かないのはなぜですか?
v7では描画される要素のクラス名が短い記号になり、v6の .fc-day-sat や .fc-day-sun が出力されないためです。dayCellClass と dayHeaderClass に関数を渡して、曜日に応じた自分のクラスを付け、そのクラスにCSSを書きます。v6の dayCellClassNames という名前ではクラスは付きません。
v6の解説記事のコードをv7で使えますか?
そのままでは動きません。@fullcalendar/core を fullcalendar に、@fullcalendar/daygrid などを fullcalendar/daygrid のサブパスに置き換え、skeleton.css とテーマのCSSを読み込む必要があります。@fullcalendar/daygrid を入れるとv6.1.21が入り、v7本体と混ざってエラーになります。