PHPackages                             ptplugins/filament-auto-filters - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. ptplugins/filament-auto-filters

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

ptplugins/filament-auto-filters
===============================

Automatic table filters for FilamentPHP based on column definitions

1.1.2(1mo ago)4197↑150%1[1 PRs](https://github.com/ptplugins/filament-auto-filters/pulls)MITPHPPHP ^8.2

Since May 4Pushed 1mo agoCompare

[ Source](https://github.com/ptplugins/filament-auto-filters)[ Packagist](https://packagist.org/packages/ptplugins/filament-auto-filters)[ RSS](/packages/ptplugins-filament-auto-filters/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (3)Dependencies (4)Versions (6)Used By (0)

Filament Auto Filters
=====================

[](#filament-auto-filters)

 [![Filament Auto Filters — auto-generated filter panel for a Filament resource](./screenshot.png)](./screenshot.png)

> Automatic table filters for [FilamentPHP](https://filamentphp.com/) **v3, v4, and v5** based on your column definitions. Stop writing repetitive filter code — just define your columns and get smart filters for free.

Single codebase across all three Filament major versions — same trait, same API.

 [![Buy us a beer](https://camo.githubusercontent.com/1bef23d3b523a63534746f730a172d71f7bc0f925da66b491b6997a778c7777c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f2546302539462538442542412d427579253230757325323061253230626565722d79656c6c6f77)](https://ptplugins.com/buy-us-a-beer)

**🎯 [Try it live · ptplugins.com/demo/auto-filters](https://ptplugins.com/demo/auto-filters)** — no signup, click any column, see filters generate themselves.

The Problem
-----------

[](#the-problem)

Every Filament resource needs filters. For a typical table with 10 columns, you end up writing 10 filter definitions — most of which follow the same patterns. Date columns get date pickers, text columns get search inputs, relationships get `whereHas` queries.

**Before** (manual filters for each column):

```
public static function table(Table $table): Table
{
    return $table
        ->columns([
            TextColumn::make('name'),
            TextColumn::make('email'),
            TextColumn::make('department.name'),
            TextColumn::make('hired_at')->date(),
            TextColumn::make('data.position'),
        ])
        ->filters([
            Filter::make('name')
                ->form([TextInput::make('value')->label('Name')])
                ->query(fn (Builder $q, array $data) => /* ... */),
            Filter::make('email')
                ->form([TextInput::make('value')->label('Email')])
                ->query(fn (Builder $q, array $data) => /* ... */),
            Filter::make('department.name')
                ->form([TextInput::make('value')->label('Department')])
                ->query(fn (Builder $q, array $data) => $q->whereRelation(/* ... */)),
            Filter::make('hired_at')
                ->form([DatePicker::make('from'), DatePicker::make('until')])
                ->query(fn (Builder $q, array $data) => /* ... */),
            Filter::make('data.position')
                ->form([TextInput::make('value')->label('Position')])
                ->query(fn (Builder $q, array $data) => /* ... using JSON arrow notation */),
        ]);
}
```

**After** (one line):

```
->filters(static::autoFilters($table))
```

Installation
------------

[](#installation)

```
composer require ptplugins/filament-auto-filters
```

The package auto-discovers its service provider. No manual registration needed.

### Publish Configuration (optional)

[](#publish-configuration-optional)

```
php artisan vendor:publish --tag=auto-filters-config
```

Quick Start
-----------

[](#quick-start)

Add the trait to your Filament resource and call `autoFilters()`:

```
use PtPlugins\FilamentAutoFilters\Concerns\HasAutoFilters;

class EmployeeResource extends Resource
{
    use HasAutoFilters;

    public static function table(Table $table): Table
    {
        return $table
            ->columns([/* ... */])
            ->filters(static::autoFilters($table));
    }
}
```

That's it. Every `TextColumn` in your table now has a filter.

Full Example: Employee Management
---------------------------------

[](#full-example-employee-management)

Let's walk through a real-world scenario. You're building an HR module with an `Employee` model that has direct columns, a `department` relationship, and a JSON `data` column for flexible fields.

### The Model

[](#the-model)

```
class Employee extends Model
{
    // Direct columns
    const NAME = 'name';
    const EMAIL = 'email';
    const HIRED_AT = 'hired_at';
    const SALARY = 'salary';

    // JSON data column fields
    const D_POSITION = 'data.position';
    const D_OFFICE = 'data.office';

    // Relationships
    const R_DEPARTMENT_NAME = 'department.name';
    const R_MANAGER_NAME = 'manager.name';

    protected $casts = [
        'hired_at' => 'date',
        'data' => 'array',
    ];

    public function department(): BelongsTo
    {
        return $this->belongsTo(Department::class);
    }

    public function manager(): BelongsTo
    {
        return $this->belongsTo(Employee::class, 'manager_id');
    }
}
```

### The Resource

[](#the-resource)

```
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use PtPlugins\FilamentAutoFilters\Concerns\HasAutoFilters;

class EmployeeResource extends Resource
{
    use HasAutoFilters;

    protected static ?string $model = Employee::class;

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make(Employee::NAME)
                    ->label('Name')
                    ->searchable(),

                TextColumn::make(Employee::EMAIL)
                    ->label('Email'),

                TextColumn::make(Employee::R_DEPARTMENT_NAME)
                    ->label('Department'),

                TextColumn::make(Employee::R_MANAGER_NAME)
                    ->label('Manager'),

                TextColumn::make(Employee::HIRED_AT)
                    ->label('Hired')
                    ->date(),

                TextColumn::make(Employee::SALARY)
                    ->label('Salary')
                    ->numeric(),

                TextColumn::make(Employee::D_POSITION)
                    ->label('Position'),

                TextColumn::make(Employee::D_OFFICE)
                    ->label('Office'),
            ])
            ->filters(static::autoFilters($table));
    }
}
```

### What Gets Generated

[](#what-gets-generated)

The plugin inspects each column and generates the right filter type. Detection rules:

ColumnFilter`TextColumn` (date / datetime)Date range picker (from / until)`TextColumn` (default)Text search (LIKE `%...%`)`TextColumn` with dot notation `rel.col`Text search via `whereRelation()``TextColumn` with `data.X` prefixText search via JSON arrow `data->X``IconColumn->boolean()`Ternary (Yes / No / All)`SelectColumn->options([...])`Select filter with same optionsOther column typesSkipped — pass an explicit filter via `overrides`For our Employee example (8 columns), all 8 filters are generated from zero lines of filter code. Same applies to a table mixing `IconColumn` and `SelectColumn` — they get their right filter automatically.

Overriding Specific Filters
---------------------------

[](#overriding-specific-filters)

Auto-generated filters are great for most columns. But sometimes you need a `SelectFilter` with specific options, or custom logic. Pass your explicit filters as `overrides` — they replace any auto-generated filter with the same name:

```
->filters(static::autoFilters($table, overrides: [
    // Replace the auto-generated text filter for department
    // with a select dropdown instead
    static::makeSelectFilter(
        Employee::R_DEPARTMENT_NAME,
        'Department',
        Department::pluck('name', 'name')
    ),

    // Custom filter with your own logic
    SelectFilter::make(Employee::SALARY)
        ->label('Salary Range')
        ->options([
            'junior' => 'Under 50k',
            'mid' => '50k - 100k',
            'senior' => 'Over 100k',
        ]),
]))
```

Result: `department.name` and `salary` use your custom filters. The remaining 6 columns still get auto-generated filters.

Skipping Columns
----------------

[](#skipping-columns)

Some columns don't need filters. Pass their names in the `skip` array:

```
->filters(static::autoFilters($table, skip: [
    'deleted_at',
    Employee::SALARY,
]))
```

Combining Overrides and Skips
-----------------------------

[](#combining-overrides-and-skips)

```
->filters(static::autoFilters($table,
    overrides: [
        static::makeSelectFilter(
            Employee::R_DEPARTMENT_NAME,
            'Department',
            Department::pluck('name', 'name')
        ),
    ],
    skip: [
        'deleted_at',
    ]
))
```

**Priority order:**

1. Override filters are always included first
2. Columns matching an override name are skipped (no duplicates)
3. Columns in the `skip` list are skipped
4. Everything else gets an auto-generated filter

API Reference
-------------

[](#api-reference)

### `autoFilters(Table $table, array $overrides = [], array $skip = []): array`

[](#autofilterstable-table-array-overrides---array-skip---array)

The main method. Inspects every column in the table and generates an appropriate filter for `TextColumn`, `IconColumn->boolean()`, and `SelectColumn`. Other column types are skipped — pass them explicitly via `overrides`.

### `makeTernaryFilter(string $name, string $label): TernaryFilter`

[](#maketernaryfilterstring-name-string-label-ternaryfilter)

Creates a yes/no/all ternary filter for a boolean column. Handles direct, JSON, and relationship columns the same way as the other helpers.

### `makeSelectFilter(string $name, string $label, array|Closure $options): SelectFilter`

[](#makeselectfilterstring-name-string-label-arrayclosure-options-selectfilter)

Creates a select dropdown filter that automatically handles:

- **Direct columns** — standard `whereIn` query
- **JSON columns** (`data.xxx`) — uses `attribute()` with arrow notation
- **Relationship columns** (`rel.col`) — uses `whereHas` query

By default, select filters are multiple-choice and searchable (configurable).

### `makeDateRangeFilter(string $name, string $label): Filter`

[](#makedaterangefilterstring-name-string-label-filter)

Creates a date range filter with "from" and "until" date pickers. Handles relationship and JSON columns the same way.

### `makeTextFilter(string $name, string $label): Filter`

[](#maketextfilterstring-name-string-label-filter)

Creates a text search filter (LIKE contains). Handles relationship and JSON columns automatically.

### `resolveColumn(string $name): array`

[](#resolvecolumnstring-name-array)

Resolves a column name into its type and query components. Used internally, but available if you need to build custom filters with the same column-detection logic.

Returns:

- `['type' => FilterType::Direct, 'query_column' => 'name']`
- `['type' => FilterType::Relationship, 'relationship' => 'department', 'column' => 'name']`
- `['type' => FilterType::Json, 'query_column' => 'data->position']`

Configuration
-------------

[](#configuration)

Publish the config file to customize defaults:

```
php artisan vendor:publish --tag=auto-filters-config
```

```
// config/auto-filters.php
return [
    'text_search_placeholder' => 'Search...', // Placeholder for text inputs
    'date_format'             => 'd.m.Y',     // Display format in filter indicators
    'select_multiple'         => true,         // Allow multi-select by default
    'select_searchable'       => true,         // Searchable dropdowns by default
    'inline_labels'           => true,         // Apply ->inlineLabel() to every auto-filter
    'prefer_pikaday'          => false,        // Use Pikaday for date filters when installed
];
```

### Optional: Pikaday date picker

[](#optional-pikaday-date-picker)

Date range filters use Filament's native `DatePicker` by default. If you install the lightweight [`ptplugins/filament-pikaday`](https://packagist.org/packages/ptplugins/filament-pikaday) package (no jQuery, no moment.js), you can route every auto-generated date filter through it instead — set `prefer_pikaday => true`:

```
composer require ptplugins/filament-pikaday
```

```
// config/auto-filters.php
'prefer_pikaday' => true,
```

The flag is a no-op when Pikaday isn't installed — auto-filters falls back to the native `DatePicker` automatically, so it's safe to enable in shared config.

`inline_labels` defaults to `true` because that's what pairs best with the recommended slide-over panel below. Set it to `false` if you keep the default Filament dropdown layout (the dropdown is narrow and inline labels can look cramped) or if you simply prefer stacked labels.

Recommended UX: Slide-Over Panel + Inline Labels
------------------------------------------------

[](#recommended-ux-slide-over-panel--inline-labels)

Out of the box Filament renders filters as a dropdown attached to a small "Filter" button — fine for one or two filters, cramped once a table has six or more. With auto-generated filters every column suddenly *has* a filter, so the dropdown stops scaling.

By default the trait applies `inlineLabel()` to every form field it generates (label on the left of the input, single row per filter — controlled by `inline_labels` in the config). That layout is *tuned for a wide panel* — pair it with a **slide-over** on the Resource and you get a "Linear / Notion"-style filter sidebar like our [live demo](https://ptplugins.com/demo/auto-filters):

```
use Filament\Tables\Table;

public static function table(Table $table): Table
{
    return $table
        ->columns([/* ... */])
        ->filters(static::autoFilters($table))
        ->filtersFormWidth('md')                         // sm | md | lg | xl
        ->filtersTriggerAction(
            fn ($action) => $action->slideOver(),         // open on the right
        );
}
```

**What changes for the user:**

- Click **Filter** → panel slides in from the right.
- Each filter is one row: `Label  [input]` (from `inlineLabel()`).
- Apply / Reset live in the panel footer; close = `Esc` or click outside.
- Table reclaims the vertical space the old dropdown took.

This is purely Filament's `Table` API — no extra CSS, no Livewire plumbing. The trait stays focused on filter *generation*; this snippet is the matching *presentation*.

### Uniform inline labels across every filter type

[](#uniform-inline-labels-across-every-filter-type)

When `inline_labels` is `true` (the default), every auto-generated filter renders with an inline label on the left and the input on the right, including the awkward cases that don't accept `inlineLabel()` directly:

Filter typeHow inline label is appliedText search (`makeTextFilter`)`TextInput::inlineLabel()` in the filter's `->form()` schemaDate range (`makeDateRangeFilter`)**Two stacked rows** (`Date from`, `Date until`) — both `DatePicker::inlineLabel()`. Avoids the asymmetric "label only on first input" layout that a single-row layout produces.Select (`makeSelectFilter`)`SelectFilter::modifyFormFieldUsing(fn ($f) => $f->inlineLabel())` (SelectFilter doesn't have a `->form()` we can put `inlineLabel()` on directly)Ternary (`makeTernaryFilter`)Same as Select — `TernaryFilter::modifyFormFieldUsing(fn ($f) => $f->inlineLabel())`. Filament's `TernaryFilter` builds its own form schema, so the regular `inlineLabel()` on a child component would never reach it.Result: every row in the slide-over reads as `[Label]  [input]` — same vertical rhythm whether the column behind it is text, date, boolean, select, JSON, or a relationship.

Set `inline_labels => false` in the published config to skip all of the above and get stacked labels instead — better suited to the default dropdown layout.

> If you write a *custom* filter (`Filter::make()->form([...])` outside this trait) and want it to match the inline style, just add `->inlineLabel()` to every form component in the schema.

How Column Detection Works
--------------------------

[](#how-column-detection-works)

The plugin uses a simple naming convention to detect column types:

```
name            → Direct column    → WHERE name LIKE '%...%'
hired_at        → Date column      → WHERE hired_at >= ? AND hired_at  $q->where('name', ...))
data.position   → JSON column      → WHERE data->position LIKE '%...%'

```

- **Dot notation** with a `data.` prefix → JSON arrow notation
- **Dot notation** without `data.` prefix → Eloquent relationship
- **No dots** → direct database column
- **Date/DateTime** detection uses Filament's built-in `isDate()` / `isDateTime()` methods on `TextColumn`

Requirements
------------

[](#requirements)

- PHP 8.2+
- FilamentPHP **3.x, 4.x, or 5.x** (single codebase across all three)
- Laravel 10, 11, or 12

License
-------

[](#license)

MIT

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance90

Actively maintained with recent releases

Popularity20

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~8 days

Total

5

Last Release

48d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/58f7e578abcf85b80036e2d54969fe0926997218a40aff6b517dd26fa38fdba9?d=identicon)[Premke](/maintainers/Premke)

---

Top Contributors

[![Premke](https://avatars.githubusercontent.com/u/1667836?v=4)](https://github.com/Premke "Premke (6 commits)")

### Embed Badge

![Health badge](/badges/ptplugins-filament-auto-filters/health.svg)

```
[![Health](https://phpackages.com/badges/ptplugins-filament-auto-filters/health.svg)](https://phpackages.com/packages/ptplugins-filament-auto-filters)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
