Filament: One Filter for Multiple Widgets with Livewire Events

In Filament, adding a filter to dashboard widgets is pretty straightforward. But what if you want to have a filter that would update MULTIPLE widgets at once, similar to Google Analytics? We need to use events and Livewire properties for this. Let's take a look.

filtered two charts


Widgets

First, let's create the widgets. We will need three widgets:

  • Form with date filters
  • Chart with orders
  • Chart with Users
php artisan make:filament-widget Filters
php artisan make:filament-widget OrdersChart --chart
php artisan make:filament-widget UsersChart --chart

For the charts, I will be using the bar type.

Now, let's show date inputs in the Filters widget with Filament Forms. Also, the Filters widget needs to be full width and have the $sort = 1 to be on top.

app/Filament/Widgets/Filters.php:

use Filament\Forms\Form;
use Filament\Forms\Components\Grid;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Concerns\InteractsWithForms;
 
class Filters extends Widget implements HasForms
{
use InteractsWithForms;
 
protected static string $view = 'filament.widgets.filters';
 
protected int | string | array $columnSpan = 'full';
 
protected static ?int $sort = 1;
 
public ?array $data = [];
 
public function form(Form $form): Form
{
return $form
->statePath('data')
->schema([
Grid::make()
->schema([
DatePicker::make('from'),
DatePicker::make('to'),
]),
]);
}
}

resources/views/filament/widgets/filters.blade.php:

<x-filament-widgets::widget>
<x-filament::section>
{{ $this->form }}
</x-filament::section>
</x-filament-widgets::widget>

In the dashboard, we now have a widget with two date inputs.

filters widget

For the other two widgets, we will show a chart of two models, User and Order. These charts will show how many new records are created daily.

For passing data to the chart, we will use the package recommended by Filament flowframe/laravel-trend.

composer require flowframe/laravel-trend

And for the charts.

app/Filament/Widgets/OrdersChart.php:

use App\Models\Order;
use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;
use Filament\Widgets\ChartWidget;
 
class OrdersChart extends ChartWidget
{
protected static ?string $heading = 'Orders Chart';
 
protected static ?int $sort = 2;
 
protected function getData(): array
{
$data = Trend::model(Order::class)
->between(
start: now()->subWeek(),
end: now(),
)
->perDay()
->count();
 
return [
'datasets' => [
[
'label' => 'Orders',
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
],
],
'labels' => $data->map(fn (TrendValue $value) => $value->date),
];
}
 
protected function getType(): string
{
return 'bar';
}
}

app/Filament/Widgets/UsersChart.php:

use App\Models\User;
use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;
use Filament\Widgets\ChartWidget;
 
class UsersChart extends ChartWidget
{
protected static ?string $heading = 'Users Chart';
 
protected static ?int $sort = 3;
 
protected function getData(): array
{
$data = Trend::model(User::class)
->between(
start: now()->subWeek(),
end: now(),
)
->perDay()
->count();
 
return [
'datasets' => [
[
'label' => 'Users',
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
],
],
'labels' => $data->map(fn (TrendValue $value) => $value->date),
];
}
 
protected function getType(): string
{
return 'bar';
}
}

We also have two charts in the dashboard showing data from the week to now.

all widgets


Updating Charts

So now, let's update the charts when a filter is selected. To achieve this, we will need to dispatch a Livewire event after the date is selected and listen for that event in both charts widgets.

app/Filament/Widgets/Filters.php:

class Filters extends Widget implements HasForms
{
// ...
 
public function form(Form $form): Form
{
return $form
->statePath('data')
->schema([
Grid::make()
->schema([
DatePicker::make('from')
->live()
->afterStateUpdated(fn (?string $state) => $this->dispatch('updateFromDate', from: $state)),
DatePicker::make('to')
->live()
->afterStateUpdated(fn (?string $state) => $this->dispatch('updateToDate', to: $state)),
]),
]);
}
}

Now we need to listen for these two events in chart widgets and set its value to a public property.

app/Filament/Widgets/OrdersChart.php:

use Livewire\Attributes\On;
use Illuminate\Support\Carbon;
 
class OrdersChart extends ChartWidget
{
protected static ?string $heading = 'Orders Chart';
 
protected static ?int $sort = 2;
 
public Carbon $fromDate;
public Carbon $toDate;
 
// ...
 
#[On('updateFromDate')]
public function updateFromDate(string $from): void
{
$this->fromDate = Carbon::make($from);
$this->updateChartData();
}
 
#[On('updateToDate')]
public function updateToDate(string $to): void
{
$this->toDate = Carbon::make($to);
$this->updateChartData();
}
}

app/Filament/Widgets/UsersChart.php:

use Livewire\Attributes\On;
use Illuminate\Support\Carbon;
 
class UsersChart extends ChartWidget
{
protected static ?string $heading = 'Users Chart';
 
protected static ?int $sort = 3;
 
public Carbon $fromDate;
public Carbon $toDate;
 
// ...
 
#[On('updateFromDate')]
public function updateFromDate(string $from): void
{
$this->fromDate = Carbon::make($from);
$this->updateChartData();
}
 
#[On('updateToDate')]
public function updateToDate(string $to): void
{
$this->toDate = Carbon::make($to);
$this->updateChartData();
}
}

Because flowframe/laravel-trend package accepts Carbon as a parameter and from the event date is sent as a string, we make it to a Carbon. And then, using the updateChartData method, the chart is updated with the new data.

All that is left is to use these two properties in the getData method.

app/Filament/Widgets/OrdersChart.php:

class OrdersChart extends ChartWidget
{
// ...
 
protected function getData(): array
{
$fromDate = $this->fromDate ??= now()->subWeek();
$toDate = $this->toDate ??= now();
 
$data = Trend::model(Order::class)
->between(
start: now()->subWeek(),
end: now(),
start: $fromDate,
end: $toDate,
)
->perDay()
->count();
 
return [
'datasets' => [
[
'label' => 'Orders',
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
],
],
'labels' => $data->map(fn (TrendValue $value) => $value->date),
];
}
 
// ...
}

app/Filament/Widgets/UsersChart.php:

class UsersChart extends ChartWidget
{
// ...
 
protected function getData(): array
{
$fromDate = $this->fromDate ??= now()->subWeek();
$toDate = $this->toDate ??= now();
 
$data = Trend::model(User::class)
->between(
start: now()->subWeek(),
end: now(),
start: $fromDate,
end: $toDate,
)
->perDay()
->count();
 
return [
'datasets' => [
[
'label' => 'Users',
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
],
],
'labels' => $data->map(fn (TrendValue $value) => $value->date),
];
}
 
// ...
}

Here, we just set a variable to the public property or a default value.


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

avatar

thank you for this great tutorial!

avatar
You can use Markdown
avatar

can you share the repo ?

avatar

Sorry we didn't save it as a separate repo, as most of the code is in the tutorial itself, anyway.

avatar
You can use Markdown
avatar

protected static string $view = 'filament.widgets.filters'

It shows empty widget.

avatar

Such a stupid mistake. You of course need to call table in the Blade {{ $this->form() }}. Will update lesson later.

avatar

I think this is what you mean

{{ $this->form }}
avatar

You are right

avatar
You can use Markdown
avatar

I'm trying to set a default date to the date component with ->default(now) but the value is not set in the component, any idea? Thx for help

avatar

Now isnt a method, you need to use as a method now()

avatar

Yes I used default(now()), but nothing is on the field

avatar
You can use Markdown
avatar

Nice tutorial :) Thx! One question: I get a 500 error, Filament\Widgets\ChartWidget::$dataChecksum

Error: Typed property Filament\Widgets\ChartWidget::$dataChecksum must not be accessed before initialization

I didn't found anything about this kind of error... any thought?

avatar

This error happens when you use the variable before it has any value. If you could, please add additional code here.

But few things to look out:

  1. Custom variable has to have a default value set (null is also a value!)
  2. You can't use a variable that has no value as it will not be initialized
avatar
You can use Markdown
avatar

More another greate tutorial, but i've a error: Cannot assign Carbon\Carbon to property Flowframe\Trend\Trend::$start of type Illuminate\Support\Carbon

https://flareapp.io/share/Lm8Q0opP

Can you help me?

avatar

Wrong namespace. Error tells you that

avatar
You can use Markdown
avatar

This is interesting. Is there a straight forward way to implement a resource form in a widget? Let's say we have a Customer and Order models, where orders belong to customer. We can go to standard CRUD for any of the resources, but could we implement a mini version of that in a widget? So we can quickly select a Customer and fill out minimum required fields for Order. I think it would be useful when on mobile phone for example, and when you gat back to a PC fill the rest of the forms with standard full model CRUD.

avatar

What do you mean in a widget?

avatar

I mean like dashboard widget with a form to add a record for a specific resource. Basically a resouce form in a custom widget to quickly add a record without clicking Orders->Create->Fill Out Form->save

avatar

In custom widget you can have whatever you want.

avatar

I figured, yes. I just mean an example for us noobs. I can't figure out how to specify a Resource model for the form in a widget.

Might even be an interesting topic to take a stab at.

avatar

Don't think you can. Treat widget as a livewire component.

avatar
You can use Markdown
avatar

Any idea how to do this same type thing using the ApexCharts plugin for filament? It's mostly working based on your tutorial, but I can't get the chart to re-render even after using ->reactive() and -afterStateUpdated()

avatar

No idea. Haven't tried it with that plugin

avatar
You can use Markdown
avatar

Good tutorial, very helpful. Thanks!

But for some reason I couldn't get this to work with Filament\Widgets\StatsOverviewWidget .. Maybe I'm using a newer version Filament.

Anyway, the official documentation also contains examples of how to implement similar functional https://filamentphp.com/docs/3.x/panels/dashboard

And it’s funny that I first found the solution on laraveldaily.com and not on the official website))

avatar

Because this festure was released just yesterday

avatar
You can use Markdown
avatar

How to handle the performance and speed issues related to use Widgets !!

the performance is so slow and bad

avatar

Hard to answer without seeing the issue and what is slow. Don't if possible but adding loading indicators should feel better atleast

avatar
You can use Markdown
avatar
You can use Markdown

Recent New Courses