Skip to content

Filtering ​

allowedFilters() lists the filters a request may use. Filters are passed as filter[<name>]=<value>:

php
QueryBuilder::for(User::class)
    ->allowedFilters('name', AllowedFilter::exact('id'))
    ->get();
GET /users?filter[name]=john&filter[id]=1,2,3

A filter not in the list throws InvalidFilterQuery.

Values ​

  • A value with the delimiter is a list: filter[id]=1,2,3 is [1, 2, 3].
  • true and false become booleans.
  • An empty value (filter[name]=) is ignored, unless the filter is nullable.

Filter types ​

Partial ​

A string, or AllowedFilter::partial('name'). Matches when the column contains the value, ignoring case: LOWER(name) LIKE '%john%'. A list matches any of its values.

AllowedFilter::beginsWith('name') and AllowedFilter::endsWith('name') match the start or the end of the column.

Exact ​

AllowedFilter::exact('id') compares with =, and a list with in. Useful for ids, statuses and booleans:

GET /users?filter[id]=1,2&filter[admin]=true

Operator ​

AllowedFilter::operator('salary', FilterOperator::GREATER_THAN) compares with a fixed operator. With FilterOperator::DYNAMIC the client puts the operator at the start of the value:

php
use MyProgs\QueryBuilder\Enums\FilterOperator;

AllowedFilter::operator('salary', FilterOperator::DYNAMIC)
GET /users?filter[salary]=>=3000

The operators are =, <>, <, >, <= and >=. A value without one compares with =. The third argument, 'or', joins the comparison with or instead of and.

Scope ​

AllowedFilter::scope('active') calls a local scope of the model with the value as its argument; a list is passed as several arguments:

php
public function scopeCreatedBetween(Builder $query, $from, $to): void
{
    $query->whereBetween('created_at', [$from, $to]);
}
php
AllowedFilter::scope('created_between')
GET /users?filter[created_between]=2026-01-01,2026-01-31

The filter name is converted to camel case to find the scope. A scope parameter typed as a model receives the model resolved by route binding, and an unknown key throws InvalidFilterValue.

Belongs to ​

AllowedFilter::belongsTo('author') filters by the key of a belongsTo relation with whereBelongsTo(): filter[author]=1,2. A dotted name filters through relations: AllowedFilter::belongsTo('post.author').

Trashed ​

AllowedFilter::trashed() for models with SoftDeletes: filter[trashed]=with includes deleted models, filter[trashed]=only returns only them, any other value leaves them out.

Callback ​

php
AllowedFilter::callback('has_posts', function (Builder $query, mixed $value, string $property) {
    $query->whereHas('posts');
})

Custom filter classes ​

Implement MyProgs\QueryBuilder\Filters\Filter and register it with custom():

php
use Illuminate\Database\Eloquent\Builder;
use MyProgs\QueryBuilder\Filters\Filter;

class FiltersUserPermission implements Filter
{
    public function __invoke(Builder $query, mixed $value, string $property): void
    {
        $query->whereHas('permissions', fn (Builder $query) => $query->where('name', $value));
    }
}
php
AllowedFilter::custom('permission', new FiltersUserPermission)

Groups ​

AllowedFilter::groupOr() applies one value to several filters joined with or, inside one parenthesised where; groupAnd() joins them with and:

php
AllowedFilter::groupOr('search', [
    AllowedFilter::partial('name'),
    AllowedFilter::partial('email'),
])
GET /users?filter[search]=john

Filtering through relations ​

A dotted name filters through a relation with whereHas():

php
AllowedFilter::exact('posts.status')
GET /users?filter[posts.status]=published

Pass false as the third argument of exact, partial, beginsWith, endsWith or operator to treat the dot as part of a column name instead (AllowedFilter::exact('meta.type', null, false)).

Options ​

Another column name ​

The second argument is the column, or the scope, behind the name the client uses:

php
AllowedFilter::exact('author', 'user_id')

Default values ​

php
AllowedFilter::exact('status')->default('published')

The default is used when the request does not mention the filter. unsetDefault() removes it.

Nullable filters ​

php
AllowedFilter::exact('deleted_at')->nullable()

An empty value (filter[deleted_at]=) then matches NULL instead of being ignored. A null default makes the filter nullable too.

Ignored values ​

php
AllowedFilter::exact('status')->ignore('all', null)

The listed values are dropped from the request value; a filter left with nothing is not applied.

Delimiter ​

php
AllowedFilter::exact('name')->delimiter('|')

The delimiter of this filter only: filter[name]=Smith, John|Doe, Jane. The default comes from the delimiter config key.