Published on

How to add quick filters (or actions) to your table

Authors
  • avatar
    Name
    Cristian Iosif
    Laravel and FilamentPHP Enthusiast
    X.com
    @X

Filament PHP is already fantastic when it comes to table customization—but sometimes you need to go beyond the default filters or actions and inject quick filter UIs right above the table, exactly where users expect them.

This tutorial walks through how I added "Departure" and "Destination" city dropdowns above a flights table using:

  • Filament's TablesRenderHook
  • Livewire components
  • Eloquent queries that react to user input

Let’s dive in! 🏊‍♂️

# Update the ListPage for Your Resource

If you’re using a resource like FlightResource, you probably already have a ListRecords page. Let’s extend it.

1class ListFlights extends ListRecords
2{
3 protected static string $resource = FlightResource::class;
4 
5 public ?int $departureCityId = null;
6 public ?int $destinationCityId = null;
7 
8 // ...
9}

We define two public properties to hold the selected city filters.

# Register Custom UI Elements using Render Hooks

Filament provides several "render hooks" to inject HTML into specific parts of the UI. For our case, we’ll hook into TOOLBAR_START to add custom Livewire filters above the table.

1use Filament\Support\Facades\FilamentView;
2use Filament\Tables\View\TablesRenderHook;
3 
4public function boot(): void
5{
6 FilamentView::registerRenderHook(
7 TablesRenderHook::TOOLBAR_START,
8 fn () => Blade::render('@livewire(\'filters.flights.departure\')')
9 );
10 
11 FilamentView::registerRenderHook(
12 TablesRenderHook::TOOLBAR_START,
13 fn () => Blade::render('@livewire(\'filters.flights.destination\')')
14 );
15}

We’re injecting two Livewire components: one for departure, and one for destination.

# Handle Events from Filter Components

To update the table based on the selected filters, we’ll listen to Livewire events.

1//...
2#[On('departureCitySelected')]
3public function handleDepartureCitySelected(?int $departureCityId): void
4{
5 $this->departureCityId = $departureCityId;
6}
7 
8#[On('destinationSelected')]
9public function handleDestinationCitySelected(?int $destinationCityId): void
10{
11 $this->destinationCityId = $destinationCityId;
12}
13//...

These event listeners update the state when a filter value is selected in the dropdowns.

# Modify the Table Query

Time to wire everything together by modifying the getTableQuery() method:

1protected function getTableQuery(): ?Builder
2{
3 return Flight::query()
4 ->when($this->departureCityId, fn ($q) => $q->where('departure_city_id', $this->departureCityId))
5 ->when($this->destinationCityId, fn ($q) => $q->where('destination_city_id', $this->destinationCityId));
6}

Now the table will automatically refresh when you update the filter values.

# Create the Filter Components

Each filter (departure and destination) is a standalone Livewire component using Filament’s form builder.

1// app/Http/Livewire/Filters/Flights/Departure.php
2 
3class Departure extends Component implements HasForms
4{
5 use InteractsWithForms;
6 
7 public ?array $data = [];
8 
9 public function mount(): void
10 {
11 $this->form->fill($this->data);
12 }
13 
14 public function form(Form $form): Form
15 {
16 return $form
17 ->schema([
18 Forms\Components\Select::make('departure_city_id')
19 ->label('')
20 ->placeholder('Select Departure')
21 ->options(City::query()->pluck('name', 'id')->unique())
22 ->afterStateUpdated(fn ($state) => $this->dispatch('departureCitySelected', departureCityId: $state))
23 ->live()
24 ])
25 ->statePath('data');
26 }
27 
28 public function render()
29 {
30 return <<<'HTML'
31 {{ $this->form }}
32 HTML;
33 }
34}
35//..
36 
37 
38 
39// app/Http/Livewire/Filters/Flights/Destination.php
40 
41class Destination extends Component implements HasForms
42{
43 use InteractsWithForms;
44 
45 public ?array $data = [];
46 
47 public function form(Form $form): Form
48 {
49 return $form
50 ->schema([
51 Forms\Components\Select::make('destination_city_id')
52 ->placeholder('Select Destination')
53 ->options(City::query()->pluck('name', 'id')->unique())
54 ->afterStateUpdated(fn ($state) => $this->dispatch('destinationCitySelected', destinationCityId: $state))
55 ->live()
56 ])
57 ->statePath('data');
58 }
59 
60 public function render()
61 {
62 return <<<'HTML'
63 {{ $this->form }}
64 HTML;
65 }
66}

# Summary

Here's what we did:

  • Extended Filament's List page to add reactive filters
  • Injected UI using TablesRenderHook
  • Created two simple Livewire components for dropdown filters
  • Updated the Eloquent query to reflect filter changes
  • Used Livewire events for real-time updates

This pattern gives you flexibility to add any kind of quick filter or custom action above your table—whether it’s tags, buttons, status toggles, or search boxes.

Advertising

Ploi referral

We take all the difficult work out of your hands, so you can focus on doing what you love: developing your application.