midsonlajeanty / laravel-filters
A package to filter Laravel models.
Fund package maintenance!
Requires
- php: ^8.3.0
- laravel/framework: ^11.0||^12.0||^13.0
Requires (Dev)
- larastan/larastan: ^2.9 || ^3.0
- laravel/pint: ^1.15
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-type-coverage: ^3.0
- phpstan/phpstan: ^1.10 || ^2.0
- ramsey/uuid: ^4.9
- rector/rector: ^2.5
- spatie/laravel-model-states: *
This package is auto-updated.
Last update: 2026-07-26 14:48:09 UTC
README
Strict, attribute-driven model filtering for Laravel.
Features
- No repetitive
ifscopes. Filter properties map directly to database columns. Attributes like#[MapTo]handle SQL operators automatically. - Strict typing. Filters are strictly typed DTOs. PHP types are automatically casted from the request, including
Carbon,Enum,Uuid,Spatie\ModelStates, and custom Value Objects. - Array expansion. Comma-separated query strings (
?tags=php,laravel) are automatically exploded into array properties. Configurable per-property via#[Delimiter]. - Decoupled architecture. Hydration and filtering flow through a pipeline of extensible Pipes. Custom operators and casters are easily injected via the registry.
- Fail-safe validation. Missing
#[Required]parameters or invalid casts instantly throw a standard LaravelValidationExceptionwith field-level errors.
Getting started
Requires PHP 8.3+ and Laravel 11.0+.
Install the package in your Laravel project:
composer require midsonlajeanty/laravel-filters
Optionally, publish the configuration to customize global delimiters, invalid value actions, casters, and operators:
php artisan vendor:publish --tag="laravel-filters-config"
Usage
Preparing the Model
Add the Filterable trait to any Eloquent model you want to filter:
use Illuminate\Database\Eloquent\Model; use Mds\LaravelFilters\Traits\Filterable; class User extends Model { use Filterable; }
Declaring a filter
Generate a new filter class:
php artisan make:filter UserFilter
Declare public properties representing your expected query string inputs. By default, properties map to columns of the same name with an = operator. PHP types are strictly enforced and automatically casted.
namespace App\Filters; use App\Enums\UserStatus; use Carbon\Carbon; use Mds\LaravelFilters\Attributes\MapTo; use Mds\LaravelFilters\Attributes\Required; use Mds\LaravelFilters\Attributes\Sorts; use Mds\LaravelFilters\QueryFilter; final class UserFilter extends QueryFilter { public function __construct( #[Required] public readonly UserStatus $status, #[MapTo('email', operator: 'contains')] public readonly ?string $search = null, #[MapTo('created_at', operator: 'gte')] public readonly ?Carbon $from_date = null, #[Sorts( allowed: ['created_at', 'name', 'email'], default: ['created_at' => 'desc'], )] public readonly array $sort = [], ) {} }
Custom filtering methods
If a property requires complex logic instead of a simple column mapping, define a method named filter + property name (PascalCase). It receives the Builder and the casted value:
final class ActiveFilter extends QueryFilter { public function __construct( public readonly bool $active, ) {} public function filterActive($query, bool $value): void { $query->whereNotNull('activated_at'); } }
Applying the filter
Type-hint the Filter in your Controller. It is hydrated directly from the Request via Laravel's container:
use App\Filters\UserFilter; class UserController extends Controller { public function index(UserFilter $filter) { return User::filter($filter)->paginate(); } }
Advanced Features
Query Name Mapping
Rename the expected query string parameter using #[QueryName]:
use Mds\LaravelFilters\Attributes\QueryName; final class UserFilter extends QueryFilter { public function __construct( #[QueryName('q')] public readonly ?string $search = null, ) {} }
Now ?q=john will populate the $search property.
Explicit Casters
Force a specific caster for a property using #[Cast]:
use Mds\LaravelFilters\Attributes\Cast; use App\Casters\MyCaster; final class UserFilter extends QueryFilter { public function __construct( #[Cast(MyCaster::class)] public readonly ?string $status = null, ) {} }
Typed Arrays
Cast each element of an array to a specific type using #[ArrayOf]:
use Mds\LaravelFilters\Attributes\ArrayOf; final class UserFilter extends QueryFilter { public function __construct( #[ArrayOf('int')] public readonly array $ids = [], ) {} }
?ids=1,2,3 will be casted to [1, 2, 3] (integers).
Custom Date Formats
Specify a date format for Carbon casting using #[DateFormat]:
use Carbon\Carbon; use Mds\LaravelFilters\Attributes\DateFormat; final class UserFilter extends QueryFilter { public function __construct( #[DateFormat('d/m/Y')] public readonly ?Carbon $birth_date = null, ) {} }
Custom Delimiters
Override the default array delimiter (,) on a per-property basis using #[Delimiter]:
use Mds\LaravelFilters\Attributes\Delimiter; final class UserFilter extends QueryFilter { public function __construct( #[Delimiter('|')] public readonly array $tags = [], ) {} }
?tags=php|laravel → ['php', 'laravel'].
Hidden Properties
Exclude a property from the toArray() output using #[Hidden]:
use Mds\LaravelFilters\Attributes\Hidden; final class UserFilter extends QueryFilter { public function __construct( #[Hidden] public readonly ?string $internal = null, ) {} }
Associative Array Operators
Pass an associative array as a query parameter to apply multiple operators on the same column:
?price[gte]=10&price[lte]=100
final class ProductFilter extends QueryFilter { public function __construct( public readonly array $price = [], ) {} }
Available built-in operators: eq, gt, gte, lt, lte, between, in, contains, starts, ends.
Artisan Generators
Generate custom operators and casters:
php artisan make:operator CustomOperator php artisan make:caster CustomCaster
Configuration
The invalid_value_action config key controls how the package behaves when a value cannot be casted:
'throw'(default): throws aValidationExceptionwith field-level errors.'ignore': silently ignores the invalid parameter.'null': sets the value tonull.
Contributing
You have a lot of options to contribute to this project! You can:
License
MIT