Combine multiple filter columns into a single Nova filter panel
degecko/laravel-nova-multifilter is a Laravel package for combine multiple filter columns into a single nova filter panel.
It currently has 0 GitHub stars and 14 downloads on Packagist (latest version v1.0.0).
Install it with composer require degecko/laravel-nova-multifilter.
Discover more Laravel packages by degecko
or browse all Laravel packages to compare alternatives.
Last updated
Combine multiple filter columns into a single Nova filter panel. Supports select dropdowns, text search, date ranges, number ranges, bitwise flags, and custom filter handlers.
composer require degecko/laravel-nova-multifilter
The service provider and assets are registered automatically via Laravel's package discovery.
npm install
npm run prod
During development:
npm run watch
In your Nova resource's filters() method:
use DeGecko\NovaMultiFilter\MultiFilter;
public function filters(NovaRequest $request): array
{
return [
new MultiFilter('Search', [
'name' => '%w%', // LIKE search (word-boundary aware)
'email' => '%#%', // LIKE search (contains)
'status' => ['active', 'inactive'], // Select dropdown
'created_at' => ['from' => null, 'to' => null], // Date range
]),
];
}
Pass an array of options. Use a flat array for value-only options, or an associative array for value => label pairs:
// Flat array — values are used as both the option value and label
'status' => ['active', 'inactive', 'banned'],
// Associative — keys are stored, values are displayed
'role' => ['admin' => 'Administrator', 'editor' => 'Editor', 'user' => 'User'],
Use a string pattern where # is replaced with the user's input:
'name' => '%w%', // Word-boundary search (splits on non-alphanumeric)
'email' => '%#%', // Simple contains
'phone' => '#%', // Starts with
The %w% pattern is special: it splits the input on non-alphanumeric characters and wraps each part with %, giving word-boundary-aware matching. For example, searching "John Doe" becomes %John%Doe%.
Pass from/to keys to render two date pickers. Either bound is optional — leaving one empty creates an open-ended range:
'created_at' => ['from' => null, 'to' => null],
'updated_at' => ['from' => null, 'to' => null],
Add 'type' => 'range' to render number inputs instead of date pickers:
'age' => ['from' => null, 'to' => null, 'type' => 'range'],
'price' => ['from' => null, 'to' => null, 'type' => 'range'],
For integer columns storing bitwise flags. Labels are auto-formatted from the keys:
'permissions' => ['bitwise', [
'can_edit' => 1,
'can_delete' => 2,
'can_publish' => 4,
]],
Prefix a column with a different display name using as:
'email as E-mail' => '%#%',
'created_at as Registered' => ['from' => null, 'to' => null],
The part before as is used for the query; the part after is displayed as the label.
You can pass existing Nova filter instances directly as columns. Their options will be extracted and their apply() method will be called when the value changes:
use App\Nova\Filters\StatusFilter;
use App\Nova\Filters\CategoryFilter;
new MultiFilter('Filters', [
'status' => new StatusFilter,
'category' => new CategoryFilter,
'name' => '%w%',
]),
Register custom handler callbacks for columns that need special query logic:
(new MultiFilter('Search', [
'name' => '%w%',
'active' => ['Yes', 'No'],
]))->handlers([
'active' => fn($value, $query) => $query->where('is_active', $value === 'Yes'),
]),
Set default filter values that are applied on page load:
(new MultiFilter('Filters', [
'status' => ['active', 'inactive', 'banned'],
'role' => ['admin', 'editor', 'user'],
]))->defaults([
'status' => 'active',
]),
Apply a scope or constraint to all filtered queries via the tap parameter:
// Scope to current tenant
new MultiFilter('Search', $columns, tap: fn($q) => $q->where('tenant_id', auth()->user()->tenant_id)),
// Include soft-deleted records
new MultiFilter('Search', $columns, tap: fn($q) => $q->withTrashed()),
Chain ->log() to output the raw SQL query to the Laravel log after filters are applied:
(new MultiFilter('Search', $columns))->log(),
use DeGecko\NovaMultiFilter\MultiFilter;
use App\Nova\Filters\StatusFilter;
public function filters(NovaRequest $request): array
{
return [
(new MultiFilter('User Search', [
'name' => '%w%',
'email as E-mail' => '%#%',
'phone' => '#%',
'status' => new StatusFilter,
'role' => ['admin' => 'Admin', 'editor' => 'Editor', 'user' => 'User'],
'created_at as Registered' => ['from' => null, 'to' => null],
'age' => ['from' => null, 'to' => null, 'type' => 'range'],
'permissions' => ['bitwise', ['can_edit' => 1, 'can_delete' => 2, 'can_publish' => 4]],
], tap: fn($q) => $q->where('tenant_id', auth()->user()->tenant_id)))
->defaults(['role' => 'user'])
->handlers([
'custom' => fn($value, $query) => $query->whereHas('profile', fn($q) => $q->where('bio', 'like', "%$value%")),
])
->log(),
];
}
The MultiFilter renders all columns in a single horizontal filter panel. Each column type (select, text, date range, bitwise) gets the appropriate input widget. Changes are debounced (100ms) and applied as a combined filter value.
On the backend, each column's value is applied to the query based on its type:
where($column, $value) or date range with whereDatewhere($column, 'like', $pattern)whereRaw("$column & ?", [$value])apply() methodMIT