lacodix/laravel-model-filter
Filter, search, and sort Eloquent models with reusable filter classes and query-string support. Includes built-in types (string, date, number, enum), relation/nested relation filtering, custom complex logic, and filter visualisation.
There are two recommended ways to create filters:
Filter class / dedicated filter class (reusable, project-wide)Fluent filter definition / inline filter definition (local per model)Use a dedicated class when you want to reuse the filter in multiple models or places.
php artisan make:filter CreatedAfterFilter -t date -f created_at
<?php
namespace App\Models\Filters;
use Lacodix\LaravelModelFilter\Enums\FilterMode;
use Lacodix\LaravelModelFilter\Filters\DateFilter;
class CreatedAfterFilter extends DateFilter
{
protected string $field = 'created_at';
public FilterMode $mode = FilterMode::GREATER_OR_EQUAL;
}
<?php
namespace App\Models;
use App\Models\Filters\CreatedAfterFilter;
use Illuminate\Database\Eloquent\Model;
use Lacodix\LaravelModelFilter\Traits\HasFilters;
class Post extends Model
{
use HasFilters;
protected array $filters = [
CreatedAfterFilter::class,
];
}
Use an inline definition when the filter is simple and only relevant for one model.
<?php
namespace App\Models;
use App\Enums\PostStatus;
use Illuminate\Database\Eloquent\Model;
use Lacodix\LaravelModelFilter\Enums\FilterMode;
use Lacodix\LaravelModelFilter\Filters\EnumFilter;
use Lacodix\LaravelModelFilter\Traits\HasFilters;
class Post extends Model
{
use HasFilters;
public function filters(): array
{
return [
EnumFilter::forModel(static::class)
->make('status')
->setTitle('Status')
->setQueryName('post_status')
->setEnum(PostStatus::class)
->setMode(FilterMode::EQUAL),
];
}
}
Factory-based creation via forModel(...)->make(...) is especially useful for typed creation and static analysis. See Typed fluent filters (PHPStan / Larastan) for details.
You can also use the make method without forModel if you don't need type-safety.
public function filters(): array
{
return [
EnumFilter::make('status')
->setTitle('Status')
->setQueryName('post_status')
->setEnum(PostStatus::class)
->setMode(FilterMode::EQUAL),
];
}
This section documents options that are shared across base filters. Filter-specific options are documented in each filter type page.
field / field()DateFilter, EnumFilter, SelectFilter, StringFilter, NumericFilter, BooleanFilter).DateFilter::make('created_at');
// or
DateFilter::make()->field('created_at');
queryName / setQueryName() / queryName()DateFilter::make('created_at')->setQueryName('created_after');
Post::filter(['created_after' => '2026-01-01'])->get();
title / setTitle() / title()EnumFilter::make('status')->setTitle('Publication status');
component / setComponent() / component()DateFilter::make('created_at')->setComponent('date-range');
mode / setMode()EQUAL, BETWEEN, CONTAINS).DateFilter::make('created_at')->setMode(FilterMode::BETWEEN);
visible()public function visible(): bool
{
return auth()->user()?->can('see-internal-filters') ?? false;
}
rules()public function rules(): array
{
return [
$this->queryName() => ['nullable', 'date'],
];
}
$messages, $validationAttributes) or methods (messages(), validationAttributes()).public array $messages = [
'created_after.date' => 'Please provide a valid date.',
];
public array $validationAttributes = [
'created_after' => 'created after',
];
table()StringFilter::make('title')->table('archived_posts');
populate()public function populate(string|array|null $values): static
{
return parent::populate($values);
}
To make a filter usable in a model, add it through $filters or the filters() method and use the HasFilters trait.
<?php
namespace App\Models;
use App\Models\Filters\CreatedAfterFilter;
use Illuminate\Database\Eloquent\Model;
use Lacodix\LaravelModelFilter\Traits\HasFilters;
class Post extends Model
{
use HasFilters;
protected array $filters = [
CreatedAfterFilter::class,
];
// Alternative solution with method:
public function filters(): array
{
return [
CreatedAfterFilter::class,
];
}
}
Now you can apply values to the filter and fetch matching models. The filter key defaults to the class basename in snake case. CreatedAfterFilter => created_after_filter
To filter programmatically use the filter scope.
Post::filter(['created_after_filter' => '2023-01-01'])->get();
To filter by query string use the filterByQueryString scope.
Post::filterByQueryString()->get();
and call the corresponding URL like this
https://.../posts?created_after_filter=2023-01-01
Filtering with multiple filters is always an AND condition. Omit filters that should not be applied.
Post::filter([
'created_after_filter' => '2023-01-01',
'published_filter' => true,
])->get();
Or via query string
https://.../posts?created_after_filter=2023-01-01&published_filter=1
With the same code as for one filter
Post::filterByQueryString()->get();
How can I help you explore Laravel packages today?