---
title: "Custom filters & sorts — Query Builder for Laravel | Roundly"
description: "Implement the Filter or Sort contract for bespoke logic, and parse the operator wire in callbacks with RequestedOperator::split()."
url: https://roundly-consulting.com/open-source/docs/query-builder-for-laravel/custom-filters-sorts
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Query Builder for Laravel](https://roundly-consulting.com/open-source/docs/query-builder-for-laravel.md)

# Custom filters & sorts

Implement the contracts to plug in bespoke logic, then register it with AllowedFilter::custom() or AllowedSort::custom():

```php
use Illuminate\Database\Eloquent\Builder;
use RoundlyConsulting\QueryBuilder\Contracts\Filter;

final class EvenViewsFilter implements Filter
{
    public function apply(Builder $query, mixed $value, string $property): void
    {
        $query->whereRaw('views % 2 = 0');
    }
}

// ...->allowedFilters(AllowedFilter::custom('even', new EvenViewsFilter))
```

```php
use Illuminate\Database\Eloquent\Builder;
use RoundlyConsulting\QueryBuilder\Contracts\Sort;
use RoundlyConsulting\QueryBuilder\Enums\SortDirection;

final class TitleLengthSort implements Sort
{
    public function apply(Builder $query, SortDirection $direction, string $property): void
    {
        $query->orderByRaw('LENGTH(title) '.$direction->value);
    }
}

// ...->allowedSorts(AllowedSort::custom('length', new TitleLengthSort))
```

## Contracts

- Filter::apply(Builder $query, mixed $value, string $property): void — $value is already normalised (comma lists are arrays; true/false stay text unless you register it with custom(…, booleans: true)); $property is the internal name.
- Sort::apply(Builder $query, SortDirection $direction, string $property): void — $direction->value is asc or desc.

## Operators in a callback

When a callback has to honour the operator wire itself, don’t hand-roll the parse — RequestedOperator::split() performs the same split the built-in filters use. The $allowed list is the whole security boundary: a token not in it is not an operator, and the whole string comes back as the value:

```php
use Illuminate\Database\Eloquent\Builder;
use RoundlyConsulting\QueryBuilder\Enums\RequestedOperator;

AllowedFilter::callback('priority', function (Builder $query, mixed $value, string $property): void {
    if (! is_string($value)) {
        return;
    }

    $parsed = RequestedOperator::split($value, [RequestedOperator::Is, RequestedOperator::Not]);

    $parsed->operatorOr(RequestedOperator::Is) === RequestedOperator::Not
        ? $query->where($property, '!=', $parsed->value)
        : $query->where($property, $parsed->value);
}),

// ?filter[priority]=high     → priority = 'high'
// ?filter[priority]=not:high → priority != 'high'
// ?filter[priority]=gte:high → priority = 'gte:high' (gte was not allowed)
```

split() returns a RequestedFilterValue: its operator is null when the request named none (which differs from naming is), and operatorOr($default) resolves it. For multi-value parameters, RequestedFilterValues::parse($raw, $allowed) returns the operator plus the list of non-empty values.

## Text matching

If a custom filter builds its own LIKE, escape user input the way the built-ins do: RoundlyConsulting\\PackageToolkit\\Support\\LikeEscaper::escape(), from the package-toolkit dependency, escapes %, \_ and \\ — and pair it with an explicit ESCAPE clause, because SQLite has no default escape character.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/query-builder-for-laravel.md)
