Build a Dynamic FullCalendar Component in Laravel Livewire

Learn how to build a dynamic FullCalendar component using Laravel Livewire 3. Load events based on calendar view, update without page reloads, and open event pages on click. Includes complete code, SEO tips, FAQ schema, and featured image.

If you’re building appointment systems, booking apps, task planners, or admin dashboards in Laravel, integrating a dynamic FullCalendar with Livewire 3 is one of the most powerful UI improvements you can make.

In this guide, we will create a SEO-optimized, production-ready FullCalendar component that:

  • Displays events dynamically
  • Loads events based on current calendar view (month/week/day)
  • Fetches events when navigation buttons are clicked
  • Refreshes events without breaking Livewire
  • Opens a single event page on event click

This tutorial is perfect for Livewire developers who want to build modern, reactive calendar features in Laravel.


Why Use FullCalendar With Livewire 3?

FullCalendar is one of the most flexible JavaScript calendar libraries available today. When combined with Livewire 3’s reactive power, you get:

  • Zero page reloads
  • Fast real-time interactions
  • Server-driven event handling
  • Clean API for filtering, loading & clicking events
  • Reusable UI components

Step 1: Install FullCalendar

Add CDN links in your layout:

<link href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.9/index.global.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.9/index.global.min.js"></script>

Step 2: Create the Livewire Calendar Component

Run the following artisan command to create a livewire component,

php artisan make:livewire calendar

It will create the following files,

  • app/Livewire/Calendar.php
  • resources/views/livewire/calendar.blade.php

Step 3: Livewire Component Logic (Calendar.php)

Copy this core logic to app/Livewire/Calendar.php file.

<?php

namespace App\Livewire;

use App\Models\Event;
use Livewire\Component;

class Calendar extends Component
{
    public $events = [];

    protected $listeners = [
        'fetchEvents' => 'loadEvents',
    ];

    public function mount()
    {
        $this->loadEvents(now()->startOfMonth(), now()->endOfMonth());
    }

    public function loadEvents($start, $end)
    {
        $this->events = Event::whereDate('start_date', '<=', $end)
            ->whereDate('end_date', '>=', $start)
            ->get()
            ->map(fn($e) => [
                'id'    => $e->id,
                'title' => $e->title,
                'start' => $e->start_date,
                'end'   => $e->end_date,
                'url'   => route('event.details', ['slug' => $e->slug])
            ])
            ->toArray();

        $this->dispatch('refreshCalendar', $this->events);
    }
    
    public function render()
    {
        return view('livewire.calendar');
    }
}

This ensures all events are always pushed correctly to the frontend.


Step 4: Blade View With Event Click Redirect

<div wire:ignore x-data="calendarComponent()" x-init="initCalendar()">
    <div id="calendar"></div>
</div>

<script>
    function calendarComponent() {
        return {
            calendar: null,

            initCalendar() {
                const calendarEl = document.getElementById('calendar');

                this.calendar = new FullCalendar.Calendar(calendarEl, {
                    initialView: 'dayGridMonth',
                    selectable: true,
                    events: @json($events),
                    headerToolbar: {
                        left: 'prev,next today',
                        center: 'title',
                        right: 'dayGridMonth,dayGridWeek,timeGridDay,listWeek'
                    },

                    datesSet: (info) => {
                        // Date range changed → fetch events
                        Livewire.dispatch('fetchEvents', {
                            start: info.startStr,
                            end: info.endStr
                        });
                    },

                    eventClick(info) {
                        info.jsEvent.preventDefault();
                        if (info.event.url) {
                            window.location.href = info.event.url;
                        }
                    },
                });

                this.calendar.render();

                // Updating events when Livewire refreshes
                Livewire.on('refreshCalendar', (events) => {
                    this.calendar.refetchEvents();
                });
            }
        }
    }
</script>

This creates a perfectly working FullCalendar with:

  • Navigational event loading
  • Correct date range handling
  • Click-to-redirect event pages
  • Safe event refresh

Step 5: Create SEO-Friendly Event URLs

Route::get('/events/{event}', [EventController::class, 'show'])->name('events.show');

Now every event opens its own page, improving SEO with schema-friendly URLs.


Benefits of This Implementation

  • Lightning-fast Livewire updates
  • Works with any event table structure
  • Perfect for admin dashboards, booking systems & CRMs
  • No page reloads
  • Mobile-friendly
  • Great for SEO (event URLs help Google index your content)

FAQ Section

1. Why are my events not appearing in FullCalendar?

Because FullCalendar requires start, end, and title fields in a strict structure.
The tutorial fixes this by mapping events manually.

2. Can I open a popup instead of redirecting on event click?

Yes. You can use Bootstrap, Alpine, Filament modal, or Livewire modal.
Just tell the preferred method.

3. Does this support drag & drop?

FullCalendar supports it.
Livewire can listen to eventDrop and update your event in DB.

4. How do I load events filtered by category or user?

Inside loadEvents(), simply add:

->where('category_id', $this->selectedCategory)

5. Does this support recurring events?

Yes — FullCalendar can expand recurring event rules during load.