Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Header Filters Laravel Package

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+.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Installation**:
   ```bash
   composer require leek/filament-header-filters

Add the HasHeaderFilters trait to your Filament resource's ListRecords page or custom table page:

use Leek\FilamentHeaderFilters\Concerns\HasHeaderFilters;

class OrderResource extends Resource
{
    public static function getPages(): array
    {
        return [
            'index' => Pages\ListOrders::route('/'),
        ];
    }
}

class ListOrders extends ListRecords
{
    use HasHeaderFilters;
}
  1. Add CSS: Update your Vite theme file (e.g., resources/css/filament/filament.css) to include:

    @import '../../../../vendor/filament/filament/resources/css/theme.css';
    @import '../../../../vendor/leek/filament-header-filters/resources/css/filament-header-filters.css';
    

    Rebuild assets:

    npm run dev
    
  2. First Use Case: Attach a SelectFilter to a column in your table definition:

    Table::make([
        TextColumn::make('status')
            ->headerFilter(
                SelectFilter::make('status')
                    ->options([
                        'pending' => 'Pending',
                        'shipped' => 'Shipped',
                        'delivered' => 'Delivered',
                    ])
            ),
    ]);
    

Implementation Patterns

Common Workflows

  1. Basic Filtering: Use built-in Filament filters (SelectFilter, DatePickerFilter, RangeFilter) directly in column headers:

    TextColumn::make('created_at')
        ->headerFilter(
            DatePickerFilter::make('created_at')
                ->native(false)
        ),
    
  2. Custom Filter Logic: Extend BaseFilter for complex logic (e.g., multi-field validation):

    class CustomStatusFilter extends BaseFilter
    {
        protected string $name = 'custom_status_filter';
    
        public function buildSchema(): array
        {
            return [
                Select::make('status')
                    ->options(['active', 'inactive'])
                    ->required(),
                Toggle::make('include_archived'),
            ];
        }
    
        public function query(Builder $query, array $data): Builder
        {
            return $query->where('status', $data['status'])
                ->when($data['include_archived'], fn($q) => $q->orWhere('archived_at', '!=', null));
        }
    }
    

    Attach it to a column:

    TextColumn::make('status')
        ->headerFilter(CustomStatusFilter::make()),
    
  3. Dynamic Options: Fetch filter options dynamically (e.g., from a database):

    TextColumn::make('category')
        ->headerFilter(
            SelectFilter::make('category')
                ->options(fn() => Category::query()->pluck('name', 'id'))
        ),
    
  4. Conditional Filters: Show/hide filters based on user roles or column visibility:

    TextColumn::make('price')
        ->visible(fn() => auth()->user()->can('view_prices'))
        ->headerFilter(
            RangeFilter::make('price')
                ->visible(fn() => auth()->user()->can('filter_prices'))
        ),
    
  5. Reusing Filters Across Columns: Define filters in a resource's getHeaderFilters() method and reuse them:

    public static function getHeaderFilters(): array
    {
        return [
            'status' => SelectFilter::make('status')
                ->options(['pending', 'shipped']),
        ];
    }
    
    // In table definition:
    TextColumn::make('status')->headerFilter('status'),
    
  6. Custom Table Pages: For custom table pages (e.g., extending Table), ensure the HasHeaderFilters trait is added after the InteractsWithTable trait to avoid boot order conflicts:

    class CustomTablePage extends Page
    {
        use InteractsWithTable;
        use HasHeaderFilters;
    
        protected static string $table = 'orders';
    }
    

Gotchas and Tips

Pitfalls

  1. CSS Conflicts:

    • If filters appear misaligned, ensure the package CSS is loaded after Filament's theme CSS.
    • Override styles in your theme file if needed (e.g., adjust padding/margins for filter dropdowns).
  2. Filter State Persistence (v2.0.4 Fix):

    • Single-Select Filters: Previously, stale array values from multi-select usage could cause blank selected values. This is now normalized in v2.0.4.
    • For custom filters, implement ->persistUsing() to ensure proper state persistence:
      SelectFilter::make('status')
          ->persistUsing(fn($state) => "status_{$state}"),
      
  3. Query Scope Conflicts:

    • Avoid mixing headerFilter() with modifyQueryUsing() or getTableFilters() for the same column, as they may override each other.
  4. Livewire Component Isolation:

    • Ensure the HasHeaderFilters trait is only added to components that extend InteractsWithTable (e.g., ListRecords or custom table pages). Adding it to unrelated components will cause errors.
    • Boot Order Note (v2.0.4): The package now registers header filters after the Filament table is initialized, resolving issues with custom HasTable pages where Livewire trait boot order differed.
  5. Filter Validation:

    • Custom filters without validation may throw errors if required fields are empty. Always include validation rules:
      public function rules(): array
      {
          return [
              'status' => ['required', Rule::in(['pending', 'shipped'])],
          ];
      }
      
  6. Multi-Select to Single-Select Migration:

    • If migrating from MultiSelectFilter to SelectFilter, clear any stale filter state in your database or session to avoid issues with normalized single-select values.

Debugging

  • Filter Not Appearing:

    • Verify the column has ->headerFilter() called.
    • Check browser console for CSS/JS errors (e.g., missing Vite assets).
    • Ensure the component using HasHeaderFilters is properly registered in the resource and extends InteractsWithTable.
  • Filter Not Working:

    • Inspect the network tab to confirm the filter payload is sent to the server.
    • Add ->log() to custom filters to debug query modifications:
      public function query(Builder $query, array $data): Builder
      {
          \Log::info('Filter data:', $data);
          return $query->where(...);
      }
      
  • Performance Issues:

    • Use ->native(true) for DatePickerFilter to leverage browser-native pickers (reduces payload size).
    • For large option sets, use ->searchable() in SelectFilter:
      SelectFilter::make('category')
          ->options(Category::query()->pluck('name', 'id'))
          ->searchable(),
      

Extension Points

  1. Custom Filter Components: Extend BaseFilter to create reusable filter types (e.g., MultiSelectHeaderFilter):

    class MultiSelectHeaderFilter extends BaseFilter
    {
        protected string $name = 'multi_select';
    
        public function buildSchema(): array
        {
            return [
                MultiSelect::make('tags')
                    ->options(['tag1', 'tag2']),
            ];
        }
    
        // ... implement query logic
    }
    
  2. Override Default Styling: Target the package's Blade components in your CSS:

    /* Target the filter dropdown container */
    .filament-header-filters-dropdown {
        width: 200px;
    }
    
    /* Style the filter icon */
    .filament-header-filters-icon {
        color: #3b82f6;
    }
    
  3. Add Icons: Use Filament's icon system to customize filter icons:

    SelectFilter::make('status')
        ->icon('heroicon-o-check-circle'),
    
  4. Internationalization: Extend the package's language lines in resources/lang/vendor/filament-header-filters:

    return [
        'filters' => [
            'select_placeholder' => 'Choose an option...',
        ],
    ];
    
  5. Server-Side Processing: For heavy filters, offload processing to a queue:

    public function query(Builder $query, array $data): Builder
    {
        FilterJob::dispatch($query, $data);
        return $query; // Return early or use a placeholder
    }
    
  6. Custom Table Pages (v2.0.4 Compatibility): When creating custom table pages, ensure proper trait ordering:

    class CustomOrdersTable extends Table
    {
        protected static string $model = Order::class;
    
        // Ensure HasHeaderFilters is loaded after InteractsWithTable
        use \Leek\FilamentHeaderFilters\Concerns\HasHeaderFilters;
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon
itsemon245/lamet
baks-dev/dashboard
amoifr/pickle-panther-bundle
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle