How to Add a Calendar to a Laravel App with Vanilla JS
By SimpleCalendarJS Team
You need to add a calendar to your Laravel app. You search Packagist and find maddhatter/laravel-fullcalendar — abandoned, 73 open issues, no Laravel 10+ support. You search npm and find FullCalendar at 43 KB+ gzipped, plus a Livewire wrapper that adds another layer of abstraction. Laravel's calendar ecosystem is built on abandoned wrappers and heavyweight libraries. A vanilla JavaScript calendar with a Blade template and a standard API route gives you a full event calendar in fewer lines and a fraction of the bundle cost.
The Laravel calendar landscape in 2026
Most Laravel calendar tutorials follow the same pattern: install a Composer wrapper around FullCalendar, wire it up through a service provider, and render events server-side. The problem is that the most popular wrapper — maddhatter/laravel-fullcalendar with 675,000+ installs and 602 GitHub stars — is officially abandoned on Packagist with no suggested replacement.
Here's what's available today:
| Library | Type | Gzipped Size | Status |
|---|---|---|---|
| maddhatter/laravel-fullcalendar | Composer wrapper | ~43 KB+ (JS) | Abandoned |
| ACT-Training/livewire-calendar | Livewire + FullCalendar | ~43 KB+ (JS) | Maintained |
| asantibanez/livewire-calendar | Livewire grid | ~15 KB (JS) | Monthly view only |
| DayPilot Lite | Vanilla JS + PHP examples | ~133 KB | Maintained |
| FullCalendar (direct) | npm / CDN | ~43 KB+ | Maintained |
| SimpleCalendarJS | npm / CDN | ~14 KB | Maintained |
Two things stand out. First, every FullCalendar-based option carries the same 43 KB+ baseline — the Composer and Livewire wrappers don't reduce the JavaScript payload, they just add PHP abstraction on top. Second, you don't need a Composer wrapper at all — FullCalendar (and every other JS calendar) works directly from a Blade template with a JSON endpoint.
The traditional approach and its problems
The classic Laravel + FullCalendar setup looks like this:
composer require maddhatter/laravel-fullcalendar
// Controller $events = []; $data = Event::all(); foreach ($data as $event) { $events[] = \Calendar::event( $event->title, false, $event->start_date, $event->end_date, ); } $calendar = \Calendar::addEvents($events); return view('calendar', compact('calendar'));
This worked in Laravel 5. The problems today:
- The package is abandoned — no updates since Laravel 5, with open issues reporting failures on Laravel 6+
- Events are rendered server-side — every page load sends the full event list in the HTML response, even if the user never scrolls to those dates
- No async fetching — navigating between months requires a full page reload or custom AJAX that bypasses the wrapper entirely
- The fork acaronlex/laravel-calendar exists but hasn't seen significant updates either
The better approach: vanilla JS + Blade (recommended)
Laravel's Blade templates give you everything you need to integrate a vanilla JavaScript calendar: a layout system for scripts, @stack directives for per-page assets, and a clean separation between your PHP backend and JavaScript frontend.
Here's how to add a calendar to a Laravel app with SimpleCalendarJS — ~14 KB gzipped, zero dependencies:
1. Install the library
npm install simple-calendar-js
Or add it to your Blade layout via CDN:
<script src="https://cdn.jsdelivr.net/npm/simple-calendar-js/dist/simple-calendar-js.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/simple-calendar-js/dist/simple-calendar-js.min.css" />
2. Create the API endpoint
// routes/api.php Route::get('/events', [EventController::class, 'index']);
// app/Http/Controllers/EventController.php namespace App\Http\Controllers; use App\Models\Event; use Illuminate\Http\Request; class EventController extends Controller { public function index(Request $request) { $events = Event::query() ->where('start_date', '>=', $request->query('from')) ->where('start_date', '<=', $request->query('to')) ->get() ->map(fn ($event) => [ 'id' => $event->id, 'title' => $event->title, 'start' => $event->start_date->toISOString(), 'end' => $event->end_date->toISOString(), 'color' => $event->color ?? '#3b82f6', ]); return response()->json($events); } }
This returns only the events within the visible date range — not the entire database table. The calendar requests new data on every navigation.
3. Add the calendar to your Blade template
<!-- resources/views/calendar.blade.php --> @extends('layouts.app') @section('content') <div class="container mx-auto py-8"> <h1 class="text-2xl font-bold mb-6">Event Calendar</h1> <div id="calendar-container" data-events-url="{{ url('/api/events') }}"></div> </div> @endsection @push('scripts') <script> document.addEventListener('DOMContentLoaded', function () { const container = document.getElementById('calendar-container'); const calendar = new SimpleCalendarJs(container, { defaultView: 'month', locale: 'en-US', enabledViews: ['month', 'week', 'day'], fetchEvents: async (start, end) => { const url = container.dataset.eventsUrl + `?from=${start.toISOString()}&to=${end.toISOString()}`; const res = await fetch(url); return res.json(); }, onEventClick: (event) => { window.location.href = `/events/${event.id}/edit`; }, onSlotClick: (date) => { window.location.href = `/events/create?date=${date.toISOString()}`; }, }); }); </script> @endpush
That's a complete, production-ready Laravel event calendar. The data-events-url attribute passes the API URL from Blade to JavaScript cleanly — no inline PHP in your script block. fetchEvents fires with the visible date range on every navigation, and onSlotClick routes to your event creation form with the selected date pre-filled.
What this gives you
- Month, week, and day views with a built-in toolbar for switching between them
- Async event fetching — only loads events within the visible date range, not the entire table
- Click handlers for existing events and empty time slots — wire to your Laravel routes for CRUD
- 34+ locales built in — pass
locale: 'pt-BR'orlocale: 'ja-JP'and the calendar renders in that language - No Composer package needed — the JavaScript library handles all rendering, your Laravel backend just serves JSON
Theming the calendar in Laravel
SimpleCalendarJS uses CSS custom properties for its entire visual layer. Add overrides in your app's stylesheet:
.uc-calendar { --cal-primary: #ef4444; --cal-primary-dark: #dc2626; --cal-today-bg: #fef2f2; --cal-font-size: 14px; } @media (prefers-color-scheme: dark) { .uc-calendar { --cal-bg: #1e1e2e; --cal-text: #cdd6f4; --cal-border: #313244; --cal-today-bg: #313244; } }
Four CSS variables and the calendar matches your Laravel app's design. No Tailwind plugin, no build-time configuration, no theme service.
Working with Livewire
If your app already uses Livewire, you don't need a Livewire-specific calendar wrapper. Use Livewire's @script directive (Livewire 3+) to initialise the calendar and call $wire methods from event handlers:
<!-- resources/views/livewire/calendar-page.blade.php --> <div> <div id="calendar-container" wire:ignore></div> </div> @script <script> const calendar = new SimpleCalendarJs($el.querySelector('#calendar-container'), { defaultView: 'month', fetchEvents: async (start, end) => { return await $wire.getEvents(start.toISOString(), end.toISOString()); }, onEventClick: (event) => $wire.editEvent(event.id), onSlotClick: (date) => $wire.createEvent(date.toISOString()), }); </script> @endscript
The wire:ignore directive tells Livewire not to diff the calendar's DOM — critical for any third-party library that manages its own rendering. The $wire proxy lets you call Livewire component methods directly from the calendar's event handlers.
Bundle size: what you're actually shipping
Laravel apps compiled with Vite (the default since Laravel 9) tree-shake and bundle your JavaScript. Every kilobyte you add to the bundle affects your Core Web Vitals — LCP and INP are Google ranking signals.
| Setup | Gzipped Size | Dependencies |
|---|---|---|
| FullCalendar standard bundle | ~43 KB | @fullcalendar/core + plugins |
| DayPilot Lite | ~133 KB | daypilot-lite-javascript |
| ACT-Training/livewire-calendar | ~43 KB+ | FullCalendar + Livewire JS |
| SimpleCalendarJS | ~14 KB | None |
SimpleCalendarJS ships a full event calendar at 3x lighter than FullCalendar and 9.5x lighter than DayPilot — with month, week, and day views included, no plugins required.
When to use a different approach
There are valid reasons to choose a heavier calendar setup:
- Real-time collaboration: If multiple users edit the same calendar simultaneously and need instant updates, a Livewire-wrapped calendar with WebSocket broadcasting handles this natively. With a vanilla JS calendar, you'd poll or integrate Laravel Echo separately.
- Resource scheduling: If you need timeline views, resource lanes, or Gantt-style layouts, DayPilot Pro or Bryntum Calendar cover those use cases — though both are enterprise-priced ($449+/year).
- Drag-and-drop event editing: FullCalendar supports dragging events between time slots out of the box. SimpleCalendarJS focuses on display and click-based interaction.
- Server-side rendering for SEO: If your calendar content needs to be crawlable (rare for event calendars), rendering events server-side in Blade templates is the only option. JavaScript calendars render client-side.
For most Laravel apps that need to display events and let users interact with them, a vanilla JavaScript calendar with a standard API route is simpler, lighter, and avoids the abandoned-wrapper problem entirely.
Summary
- Laravel's most popular calendar wrapper (maddhatter/laravel-fullcalendar) is abandoned — 73 open issues, no Laravel 10+ support, no suggested replacement
- You don't need a Composer wrapper — vanilla JavaScript calendars work directly with Blade templates and a JSON API route
- FullCalendar works without a wrapper but still adds ~43 KB+ gzipped to your bundle — plus you need separate plugin packages for each view
- SimpleCalendarJS delivers month, week, and day views in ~14 KB gzipped — install via npm or CDN, initialise in a
@push('scripts')block, and pointfetchEventsat your API route - The Livewire integration pattern (
wire:ignore+@script+$wire) works with any vanilla JS library — no framework-specific wrapper needed
Sources & Further Reading
Research & References
- maddhatter/laravel-fullcalendar — Abandoned on Packagist
- Not working in Laravel 6 — maddhatter/laravel-fullcalendar — GitHub #152
- ACT-Training/livewire-calendar — FullCalendar Livewire Wrapper — GitHub
- asantibanez/livewire-calendar — Monthly Calendar Component — GitHub
- Laravel Appointment Calendar: Simple FullCalendar Demo — LaravelDaily
- How to Create Event Calendar in Laravel 12 — ItSolutionStuff
- FullCalendar v6 bundle size increase — GitHub #7029
- HTML5/JavaScript Calendar with Day/Week/Month Views (PHP, MySQL) — DayPilot
- DayPilot Lite for JavaScript — npm
Image Credits
- Cover: PHP Screengrab — Pexels
All images free to use under the Pexels License.
Frequently Asked Questions
What is the best calendar library for Laravel?
It depends on your needs. For a full event calendar with month, week, and day views, SimpleCalendarJS (~14 KB gzipped) integrates with any Laravel Blade template via a script tag or npm — no Composer wrapper needed. For Livewire-specific reactivity, ACT-Training/livewire-calendar wraps FullCalendar. For enterprise scheduling with resource views, DayPilot or Bryntum offer PHP backend examples but cost significantly more.
How do I add a calendar to a Laravel Blade template?
Install a vanilla JavaScript calendar library via npm or CDN, add a container div to your Blade view, and initialise the calendar in a script block. Use Laravel's route() helper or a data attribute to pass API endpoints for event fetching. No Composer package or Livewire component is required.
Is maddhatter/laravel-fullcalendar still maintained?
No. The maddhatter/laravel-fullcalendar package is officially abandoned on Packagist with no suggested replacement. It has 73 open issues, including compatibility problems with Laravel 6 and above. Most Laravel calendar tutorials still reference it, but you should use FullCalendar directly via npm or choose a lighter alternative.
Can I use FullCalendar with Laravel without a wrapper package?
Yes. You can install FullCalendar via npm or load it from a CDN, then initialise it in your Blade template. The wrapper packages (maddhatter, acaronlex) were convenience layers for server-side event rendering, but FullCalendar's JavaScript API works directly with any JSON endpoint — no PHP wrapper needed.
How much does a JavaScript calendar add to my Laravel app's bundle size?
Bundle sizes vary widely. The FullCalendar standard bundle adds ~43 KB gzipped. DayPilot Lite adds ~133 KB gzipped. SimpleCalendarJS adds ~14 KB gzipped with zero dependencies. Since Laravel apps serve JavaScript as static assets, every kilobyte affects your page load time and Core Web Vitals scores.
Do I need Livewire to add a calendar to Laravel?
No. Livewire is useful if your calendar needs to react to server-side state changes in real time (e.g. live-updating a shared team schedule). For most use cases — displaying events, letting users click to create or edit — a vanilla JavaScript calendar with a standard Laravel API route is simpler and avoids the Livewire overhead.
Add a calendar to your app today
Free for personal projects. $49/year or $199 lifetime per commercial project.
