Filament: Show Calendar of Tasks with FullCalendar

In this tutorial, we will show you how to quickly create a calendar in Filament for your model to display Tasks, Events, etc, using the package called "Filament FullCalendar".

Here, we have the Task Model that contains the name (string), start (datetime), and end (datetime) fields.

Filament FullCalendar

Install the Filament FullCalendar package via Composer.

composer require saade/filament-fullcalendar:^3.0

Register FilamentFullCalendarPlugin in the AdminPanelProvider file.

app/Providers/Filament/AdminPanelProvider.php

use Saade\FilamentFullCalendar\FilamentFullCalendarPlugin;
 
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugins([FilamentFullCalendarPlugin::make()]);
}

Then, create a widget.

php artisan make:filament-widget CalendarWidget

Replace CalendarWidget contents as follows.

app/Filament/Widgets/CalendarWidget.php

namespace App\Filament\Widgets;
 
use App\Models\Task;
use Illuminate\Database\Eloquent\Model;
use Saade\FilamentFullCalendar\Widgets\FullCalendarWidget;
 
class CalendarWidget extends FullCalendarWidget
{
public Model | string | null $model = Task::class;
 
public function fetchEvents(array $fetchInfo): array
{
return Task::where('start', '>=', $fetchInfo['start'])
->where('end', '<=', $fetchInfo['end'])
->get()
->map(function (Task $task) {
return [
'id' => $task->id,
'title' => $task->name,
'start' => $task->start,
'end' => $task->end,
];
})
->toArray();
}
 
public static function canView(): bool
{
return false;
}
}

The fetchEvents() method returns your model data in a specific format, so you must map the data from your model.

  • id - Model's id
  • title - Property you want to display as calendar title
  • start - DateTime value when the Task starts
  • end - DateTime value when the Task ends

By default, CalendarWidget is displayed on the Dashboard page. The canView() returns a boolean whether to display the widget on the Dashboard or not. It may contain your custom logic.

For this example, we return false to completely hide it because we will display it on a separate page.

Let's create the said page.

php artisan make:filament-page Calendar

Finally, update the page contents.

resources/views/filament/pages/calendar.blade.php

<x-filament-panels::page>
@livewire(\App\Filament\Widgets\CalendarWidget::class)
</x-filament-panels::page>

If you want more Filament examples, you can find more real-life projects on our FilamentExamples.com.

avatar

Hi!

This gives me an error: "Typed static property Filament\Widgets\Widget::$view must not be accessed before initialization"

I'm using Laravel 10 and Filament 3

avatar

check if you really extend FullCalendarWidget instead of Widget

avatar

thank you very much! It was that

avatar

I get this error when pressing "New Task" top right Action:

Filament\Actions\CreateAction::Filament\Actions{closure}(): Argument #2 ($form) must be of type Filament\Forms\Form, null given, called in /Users/d4n333/work/valet/xxx/vendor/filament/support/src/Concerns/EvaluatesClosures.php on line 35

For my Tasks I use simple Resource. Is this the problem? Should I use Pages for Create and Edit the Tasks?

avatar

Do you actually have a create form page and form() defined in your resource?

This tutorial is only for displaying calendar. Please see plugin documentation.

avatar

thx david, fullcalendars documentation helped me out.

avatar
Jose Antonio Rojas Rodríguez

Same error here, how did you solve it?

avatar

why can't i see the timegrid? i don't see the hours inside the day

avatar
Oreste Barranco Hernández

Great tutorial. How could I add filters to show events within a given date for example?

avatar

hey guys. I want to change some appearance using slotLabelContent function on resourceTimeline view. I can't get arg object on function. how to do it? please help.

<?php

namespace App\Filament\Widgets;

use Saade\FilamentFullCalendar\Widgets\FullCalendarWidget;

class CalendarWidget extends FullCalendarWidget
{
    public function config(): array
    {
        return [
            'initialView' => 'resourceTimelineDefault',
            'headerToolbar' => [
                'left' => 'dayGridWeek,dayGridDay',
                'center' => 'title',
                'right' => 'prev,next today',
            ],
            'views' => [
                'resourceTimelineDefault' => [
                    'type' => 'resourceTimeline',
                    'duration' => ['days' => 4],
										// I cant get arg object here
                    'slotLabelContent' => function($arg) {
                        if ($arg.level === 1) {
                          $time = $arg.date.getHours();
                          $timeLabel = '';
              
                          if ($time >= 0 && $time < 8) {
                            $timeLabel = 'Morning';
                          } else if ($time >= 8 && $time < 16) {
                            $timeLabel = 'Afternoon';
                          } else if ($time >= 16 && $time < 24) {
                            $timeLabel = 'Night';
                          }
              
                          return [
                              'html' => '<div class="custom-label-top">' + $arg.text + '</div>' +
                                    '<div class="custom-label-bottom">' + $timeLabel + '</div>'

                          ];
                        } else {
                          return [
                              'html' => '<div class="custom-label-top">' + arg.text + '</div>'
                          ];
                        }
                      },
                ]
            ]
        ];
    }

}
avatar

How can we implement a filtering system above the calendar? For instance, when displaying orders in the calendar view, we could add a form at the top that allows users to filter orders based on their status.

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 66 courses (1167 lessons, total 43 h 18 min)
  • 88 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent New Courses