leek/filament-header-filters
Add inline filters to Filament table column headers. Attach any BaseFilter (selects, date pickers, min/max ranges, custom schemas) as a richer alternative to individual searchable fields. Works with Filament v4/v5, PHP 8.2+.
Installation:
composer require leek/filament-header-filters
Add the HasHeaderFilters trait to your ListRecords or custom Livewire table component:
use Leek\FilamentHeaderFilters\Concerns\HasHeaderFilters;
class ListOrders extends ListRecords
{
use HasHeaderFilters;
}
CSS Integration: Add the package stylesheet to your Filament panel theme after Filament’s theme import:
@import '../../../../vendor/filament/filament/resources/css/theme.css';
@import '../../../../vendor/leek/filament-header-filters/resources/css/filament-header-filters.css';
Rebuild assets:
npm run build
First Use Case:
Attach a SelectFilter to a column header for inline filtering:
TextColumn::make('status')
->badge()
->headerFilter(
SelectFilter::make('status')
->options(OrderStatus::class)
->native(false)
)
TextColumn::make('status')
->headerFilter(
SelectFilter::make('status')
->options(OrderStatus::class)
->searchable()
)
TextColumn::make('price')
->headerFilter(
Filter::make('price_range')
->columns(2)
->schema([
TextInput::make('min')->numeric(),
TextInput::make('max')->numeric(),
])
->query(fn (Builder $query, array $data) => $query
->when($data['min'], fn ($q, $v) => $q->where('price', '>=', $v))
->when($data['max'], fn ($q, $v) => $q->where('price', '<=', $v))
)
)
TextColumn::make('created_at')
->headerFilter(
Filter::make('date_range')
->columns(2)
->schema([
DatePicker::make('from')->native(false),
DatePicker::make('until')->native(false),
])
->query(fn (Builder $query, array $data) => $query
->when($data['from'], fn ($q, $v) => $q->whereDate('created_at', '>=', $v))
->when($data['until'], fn ($q, $v) => $q->whereDate('created_at', '<=', $v))
)
)
$tableFilters). Use ->deferFilters() on the table if you need to control when filters apply.->reset() on individual filters.Extend BaseFilter for complex logic:
class CustomStatusFilter extends Filter
{
protected string $columnName = 'status';
public function query(Builder $query, array $data): Builder
{
return $query->where('status', $data['status'] ?? null);
}
}
Attach it to a column:
TextColumn::make('status')->headerFilter(CustomStatusFilter::make());
Fetch options dynamically (e.g., from a relationship):
SelectFilter::make('user_id')
->options(fn () => User::query()->pluck('name', 'id'))
->headerFilter()
Show/hide filters based on context:
TextColumn::make('priority')
->headerFilter(
SelectFilter::make('priority')
->options(['low', 'medium', 'high'])
->visible(fn () => auth()->user()->can('filter_priority'))
)
Initialization Order:
HasTable pages, ensure HasHeaderFilters is loaded after InteractsWithTable. The trait now registers filters post-initialization (fixed in v2.0.4).$this->table->getHeaderFilters() in mount() if issues persist.Stale State in Single-Select Filters:
['pending'] instead of 'pending'). Fixed in v2.0.4, but test edge cases like rapid toggling between single/multi-select modes.CSS Conflicts:
.filament-header-filters {
z-index: 1000 !important;
}
native(false) for better styling consistency.Hidden Columns:
->visible() to toggle visibility dynamically.Filacheck False Positives:
missing-table-filters rule may flag tables using only header filters. Disable it in config/filacheck.php:
'missing-table-filters' => ['enabled' => false],
Asset Rebuilding:
npm run build after adding the CSS will break filter rendering. Use npm run dev in development.dd($this->table->getHeaderFilters());
tap() to debug the query builder:
->query(fn (Builder $query) => $query->tap(fn ($q) => dd($q->toSql())))
dd($this->getTableHeaderFiltersForm()->getState());
Custom Filter Components:
Extend BaseFilter and override getField() to use custom form components:
class CustomFilter extends Filter
{
public function getField(): array
{
return [
Toggle::make('active')->label('Active'),
];
}
}
Override Table View:
If Filament updates break the view override, copy filament-tables::index from the package to resources/views/vendor/filament-tables/ and modify as needed.
Dynamic Column Names:
Use ->columnName() in custom filters to dynamically set the column:
Filter::make('dynamic_filter')
->columnName('dynamic_column')
->schema([/* ... */])
Session Persistence: Leverage Filament’s built-in session persistence for filters. Ensure your table uses:
->persistFilters()
->query() closures in filters can impact performance. Cache dynamic options (e.g., ->options(fn () => cache()->remember(...))).TextInput::make('search')
->debounce(500)
->searchable() and paginate options:
SelectFilter::make('user_id')
->options(fn () => User::query()->paginate(20))
->searchable()
public function test_header_filter()
{
$filter = SelectFilter::make('status')->options(['active', 'inactive']);
$column = TextColumn::make('status')->headerFilter($filter);
$this->assertTrue($column->hasHeaderFilter());
}
$this->livewire(ListOrders::class)
->call('getTableHeaderFiltersForm')
->assertSet('status', 'active');
How can I help you explore Laravel packages today?