Skip to content

feat: add headerFilter() to table columns for inline filter components#19432

Open
leek wants to merge 2 commits intofilamentphp:4.xfrom
leek:feat/header-filters
Open

feat: add headerFilter() to table columns for inline filter components#19432
leek wants to merge 2 commits intofilamentphp:4.xfrom
leek:feat/header-filters

Conversation

@leek
Copy link
Contributor

@leek leek commented Mar 5, 2026

CleanShot 2026-03-05 at 12 34 34@2x-optimised

Summary

Adds a new headerFilter() method to table columns, allowing any BaseFilter subclass to render inline under the column header. This provides a richer alternative to searchable(isIndividual: true) — instead of a plain text search input, users can attach select dropdowns, date pickers, custom multi-field schemas, and more directly to column headers.

Examples

// Select filter under column header
TextColumn::make('status')
    ->headerFilter(
        SelectFilter::make('status')
            ->options(OrderStatus::class)
            ->native(false)
    )

// Custom range filter with side-by-side fields
TextColumn::make('price')
    ->headerFilter(
        Filter::make('price')
            ->columns(2)
            ->schema([
                TextInput::make('min')->numeric()->placeholder('Min'),
                TextInput::make('max')->numeric()->placeholder('Max'),
            ])
            ->query(fn (Builder $query, array $data) => $query
                ->when($data['min'] ?? null, fn ($q, $v) => $q->where('price', '>=', $v))
                ->when($data['max'] ?? null, fn ($q, $v) => $q->where('price', '<=', $v))
            )
    )

Design

  • Header filters use a separate Livewire state ($tableHeaderFilters) and form (tableHeaderFiltersForm) — fully isolated from panel filters
  • Always live — applies immediately on change, regardless of deferFilters()
  • Filter field labels are auto-hidden (the column header serves as the label)
  • Multi-field schemas support ->columns() for side-by-side layout with dense gap
  • Fully backwards-compatible — searchable(isIndividual: true) is unchanged
  • Supports filter indicators, reset, session persistence, and records() data sources

Fixes

Fixes #18108
Fixes #15714
Fixes #8879
Fixes #6842

Test Plan

  • Column with SelectFilter renders a select under the header and filters records by exact match
  • Column with custom Filter schema renders inline and applies the custom query
  • searchable(isIndividual: true) still works unchanged
  • Header filter indicators appear and can be removed
  • resetTableFiltersForm() resets header filters
  • Hidden columns' header filters don't apply to the query
  • Hidden filters (->hidden()) don't render in the header row
  • Non-query records() tables receive headerFilters in the callback
  • PHPStan passes with no new errors

Allows attaching any BaseFilter subclass (SelectFilter, Filter with
custom schema, etc.) to a table column via `->headerFilter()`. The
filter renders inline under the column header as a richer alternative
to `searchable(isIndividual: true)`.

- Separate Livewire state ($tableHeaderFilters) and form, isolated
  from panel filters
- Always live (ignores deferFilters())
- Auto-hidden labels, dense gap for multi-field schemas
- Supports indicators, reset, session persistence, records() sources
- Fully backwards-compatible

Fixes filamentphp#18108, filamentphp#15714, filamentphp#8879, filamentphp#6842
@leek leek marked this pull request as ready for review March 5, 2026 18:13
@danharrin danharrin added enhancement New feature or request pending review labels Mar 6, 2026
@danharrin danharrin modified the milestone: v4 Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request pending review

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants