# Filters

# What filter types are available?

As of `15/09/2026`, there are eleven filter types available:

<table border="1" id="bkmrk-type-description-inv" style="border-collapse: collapse; width: 100%; height: 575.922px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**Type**  
</td><td style="height: 29.7969px;">**Description**  
</td><td style="height: 29.7969px;">**Invertible?**  
</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">Dropdown</td><td style="height: 46.5938px;">Allows users to select a single option from a dropdown menu.</td><td style="height: 46.5938px;">Yes</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">Radio List</td><td style="height: 46.5938px;">Allows users to select exactly one option from a list of radio buttons.</td><td style="height: 46.5938px;">Yes</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">Checkbox List</td><td style="height: 46.5938px;">Allows users to select one or more options from a list of checkboxes.</td><td style="height: 46.5938px;">Yes</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">Min Max</td><td style="height: 46.5938px;">Allows users to specify minimum and maximum values for a numeric range.</td><td style="height: 46.5938px;">No</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">Min Max Price</td><td style="height: 46.5938px;">Allows users to specify minimum and maximum values for a price range.</td><td style="height: 46.5938px;">No</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">Date Range</td><td style="height: 46.5938px;">Allows users to filter results by selecting a start date and end date.</td><td style="height: 46.5938px;">No</td></tr><tr style="height: 80.1875px;"><td style="height: 80.1875px;">Options Date Range</td><td style="height: 80.1875px;">A Date Range Filter that also allows users to select a predefined date range option, such as `Today`, `Last 7 days`, or `Last 30 days`.</td><td style="height: 80.1875px;">No</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">Searchable Select</td><td style="height: 46.5938px;">Allows users to search for and select an option from a searchable list.</td><td style="height: 46.5938px;">Yes</td></tr><tr style="height: 80.1875px;"><td style="height: 80.1875px;">Searchable Select When Over Count</td><td style="height: 80.1875px;">Functions as a Checkbox List Filter when there's fewer than a certain number of options, in which case it then functions as a Searchable Select Filter.</td><td style="height: 80.1875px;">Yes</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Searchable Select With Modal</td><td style="height: 29.7969px;">A Searchable Select Filter that supports searching via modal.</td><td style="height: 29.7969px;">Yes</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Searchable Select With Product Picker</td><td style="height: 29.7969px;">A Searchable Select Filter for products that supports searching via product picker.</td><td style="height: 29.7969px;">Yes</td></tr></tbody></table>

---

### Dropdown Filter

```php
class ProductStockLevelViewAdminFilter extends \Aero\Admin\Filters\DropdownAdminFilter
{
    protected function handleDropdown($selected, $query)
    {
        if ($selected === 'only-out-of-stock') {
            $query->where('stock_level', '<=', 0);
        } elseif ($selected === 'only-in-stock') {
            $query->where('stock_level', '>', 0);
        } elseif ($selected === 'only-negative') {
            $query->where('stock_level', '<', 0);
        }
    }

    protected function dropdowns(): array
    {
        return [
            [
                'name' => 'Show all',
                'value' => '',
            ],
            [
                'name' => 'Only show out of stock',
                'value' => 'only-out-of-stock',
            ],
            [
                'name' => 'Only show in stock',
                'value' => 'only-in-stock',
            ],
            [
                'name' => 'Only show negative stock',
                'value' => 'only-negative',
            ],
        ];
    }

    public function baseName(): string
    {
        return 'View';
    }
}
```

---

### Radio List Filter

```php
class OrderFlagStatusAdminFilter extends \Aero\Admin\Filters\RadioListAdminFilter
{
    protected function handleRadioList($selected, $query)
    {
        return $query
            ->when($selected === 'not', function ($query) {
                $query->whereDoesntHave('flags');
            })
            ->when($selected === 'has', function ($query) {
                $query->whereHas('flags');
            });
    }

    protected function radios(): array
    {
        return [
            [
                'value' => 'has',
                'name' => 'Has been flagged',
            ],
            [
                'value' => 'not',
                'name' => 'Has not been flagged',
            ],
        ];
    }

    public function baseName(): string
    {
        return 'FlagStatus';
    }
}
```

---

### Checkbox List Filter

```php
class RedirectsStatusAdminFilter extends \Aero\Admin\Filters\CheckboxListAdminFilter
{
    protected function handleCheckboxList($selected, $query)
    {
        $query->whereIn('status', $selected);
    }

    protected function checkboxes(): array
    {
        return [
            [
                'id' => '301',
                'name' => 'Moved Permanently (301)',
                'url' => $this->getUrlFor('301'),
            ],
            [
                'id' => '302',
                'name' => 'Found Redirect (302)',
                'url' => $this->getUrlFor('302'),
            ],
            [
                'id' => '308',
                'name' => 'Permanent Redirect (308)',
                'url' => $this->getUrlFor('308'),
            ],
        ];
    }

    protected function baseName(): string
    {
        return 'Status';
    }
}
```

<p class="callout info">`getUrlFor` is a helper method provided by the `Aero\Admin\Filters\Traits\AdminFilterHelpers` trait.</p>

---

### Min Max Filter

```php
class ProductStockLevelStockAdminFilter extends \Aero\Admin\Filters\MinMaxAdminFilter
{
    public function handleMinMax($min, $max, $query)
    {
        $query
            ->when($min !== null, function ($query) use ($min) {
                $query->where('stock_level', '>=', $min);
            })
            ->when($max !== null, function ($query) use ($max) {
                $query->where('stock_level', '<=', $max);
            });
    }

    public function baseName(): string
    {
        return 'Stock';
    }
}
```

---

### Min Max Price Filter

```php
class VoucherBalanceAdminFilter extends \Aero\Admin\Filters\MinMaxPriceAdminFilter
{
    public function handleMinMax($min, $max, $query)
    {
        return $query
            ->when($min !== null, function ($query) use ($min) {
                $query->where('balance', '>=', $min);
            })
            ->when($max !== null, function ($query) use ($max) {
                $query->where('balance', '<=', $max);
            });
    }

    public function baseName(): string
    {
        return 'Balance';
    }
}
```

---

### Date Range Filter

```php
class CustomerLastLoginAtAdminFilter extends \Aero\Admin\Filters\DateRangeAdminFilter
{
    protected function handleDateRange($startDate, $endDate, $query)
    {
        $query->whereBetween('last_login_at', [$startDate, $endDate]);
    }

    protected function baseName(): string
    {
        return 'LastLoginAtDate';
    }
}
```

---

### Options Date Range Filter

```php
class ExportLogCreatedAtDateAdminFilter extends \Aero\Admin\Filters\OptionsDateRangeAdminFilter
{
    protected $extraDates = true;

    protected function handleDateRange($startDate, $endDate, $query): void
    {
        $query->whereBetween('created_at', [$startDate, $endDate]);
    }

    protected function baseName(): string
    {
        return 'CreatedAtDate';
    }
}
```

---

### Searchable Select Filter

```php
class OrderDiscountAdminFilter extends \Aero\Admin\Filters\SearchableSelectAdminFilter
{
    protected $multiple = true;

    protected function handleSearchableSelect(array $selected, $query): void
    {
        $query->whereHas('discounts', function ($query) use ($selected) {
            $query->whereIn('id', $selected);
        });
    }

    protected function model(): \Aero\Common\Models\Model
    {
        return new \Aero\Cart\Models\Discount();
    }

    protected function modelName(\Aero\Common\Models\Model $model)
    {
        return $model->code ?? ($model->name ?? 'Discount #'.$model->id);
    }

    protected function searchRoute(): string
    {
        return route('admin.discounts.search');
    }

    protected function baseName(): string
    {
        return 'Discount';
    }
}
```

---

### Searchable Select When Over Count Filter

```php
class CurrencyAdminFilter extends \Aero\Admin\Filters\SearchableSelectWhenOverCountAdminFilter
{
    protected function handleSearchableSelectWhenOverCount(array $selected, $query)
    {
        $query->whereHas('currency', function ($query) use ($selected) {
            $query->whereIn('code', $selected);
        });
    }

    protected function model(): \Aero\Common\Models\Model
    {
        return new \Aero\Common\Models\Currency();
    }

    protected function modelName(\Aero\Common\Models\Model $model): string
    {
        return $model->name . ' (' . $model->code . ')';
    }

    protected function searchRoute(): string
    {
        return route('admin.configuration.currencies.search');
    }

    protected function whenOverCount(): int
    {
        return 10;
    }

    protected function baseName(): string
    {
        return 'Currency';
    }
}
```

---

### Searchable Select With Modal Filter

```php
class PriceListCategoryAdminFilter extends \Aero\Admin\Filters\SearchableSelectWithModalAdminFilter
{
    protected $multiple = true;

    protected function handleSearchableSelect(array $selected, $query)
    {
        $query->whereHas('entries', function ($query) use ($selected) {
            $query->whereHasMorph('catalog', [\Aero\Catalog\Models\Category::class], function ($query) use ($selected) {
                $query->whereIn('id', $selected);
            });
        });
    }

    protected function model(): \Aero\Common\Models\Model
    {
        return new \Aero\Catalog\Models\Category();
    }

    protected function modelName(\Aero\Common\Models\Model $model)
    {
        return $model->breadcrumb->map->name->implode(' » ');
    }

    protected function searchRoute(): string
    {
        return route('admin.catalog.categories.search');
    }

    protected function modalRoute(): string
    {
        return route('admin.catalog.categories.search.tree');
    }

    public function baseName(): string
    {
        return 'Category';
    }
}
```

---

### Searchable Select With Product Picker Filter

```php
class PriceListProductAdminFilter extends \Aero\Admin\Filters\SearchableSelectWithProductPickerAdminFilter
{
    protected function handleSearchableSelect(array $selected, $query)
    {
        $query->whereHas('entries', function ($query) use ($selected) {
            $query->whereHasMorph('catalog', [\Aero\Catalog\Models\Product::class], function ($query) use ($selected) {
                $query->whereIn('id', $selected);
            });
        });
    }

    public function baseName(): string
    {
        return 'Product';
    }
}
```

# How do I make my filter invertible?

As of `15/09/2026`, the following filters support being inverted:

<table border="1" id="bkmrk-type-handle-method-i" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 33.3731%;"></col><col style="width: 33.3731%;"></col><col style="width: 33.3731%;"></col></colgroup><tbody><tr><td>**Type**  
</td><td>**Handle Method**  
</td><td>**Inverted Handle Method**  
</td></tr><tr><td>Dropdown</td><td>`handleDropdown`</td><td>`handleInvertedDropdown`</td></tr><tr><td>Radio List</td><td>`handleRadioList`  
</td><td>`handleInvertedRadioList`  
</td></tr><tr><td>Checkbox List</td><td>`handleCheckboxList`  
</td><td>`handleInvertedCheckboxList`  
</td></tr><tr><td>Searchable Select</td><td>`handleSearchableSelect`  
</td><td>`handleInvertedSearchableSelect`  
</td></tr><tr><td>Searchable Select When Over Count</td><td>`handleSearchableSelectWhenOverCount`  
</td><td>`handleInvertedSearchableSelectWhenOverCount`</td></tr><tr><td>Searchable Select With Modal</td><td>`handleSearchableSelect`  
</td><td>`handleInvertedSearchableSelect`  
</td></tr><tr><td>Searchable Select With Product Picker</td><td>`handleSearchableSelect`  
</td><td>`handleInvertedSearchableSelect`  
</td></tr></tbody></table>

To enable inverted handling, set `$inverted` to `true` on the filter:

```php
class RedirectsStatusAdminFilter extends \Aero\Admin\Filters\CheckboxListAdminFilter
{
    protected $invertible = true;

    protected function handleCheckboxList($selected, $query)
    {
        $query->whereIn('status', $selected);
    }

    protected function handleInvertedCheckboxList(array $selected, $query): void
    {
        $query->whereNotIn('status', $selected);
    }

    protected function checkboxes(): array
    {
        return [
            [
                'id' => '301',
                'name' => 'Moved Permanently (301)',
                'url' => $this->getUrlFor('301'),
            ],
            [
                'id' => '302',
                'name' => 'Found Redirect (302)',
                'url' => $this->getUrlFor('302'),
            ],
            [
                'id' => '308',
                'name' => 'Permanent Redirect (308)',
                'url' => $this->getUrlFor('308'),
            ],
        ];
    }

    protected function baseName(): string
    {
        return 'Status';
    }
}
```

<p class="callout info">The inverted handler is optional for Laravel 10+ (in most cases), as it is automatically generated using `whereNot`.</p>

# How do I create a custom admin filter?

To create a custom admin filter you must create a class that extends `\Aero\Admin\Filters\AdminFilter` and implement a `handle` method:

```php
class OrderStatusAdminFilter extends \Aero\Admin\Filters\AdminFilter
{
    use \Aero\Admin\Filters\Traits\HasDefault;
    use \Aero\Admin\Filters\Traits\AdminFilterHelpers;

    protected $view = 'admin::filters.order-status';

    protected function selectedOptions(): array
    {
        return request()->input($this->key(), []) ?? [];
    }

    public function handle(\Symfony\Component\HttpFoundation\ParameterBag $parameters, $query)
    {
        $view = $parameters->get('view');

        if ($view === null && strlen($parameters->get('q', ''))) {
            $view = 'all';

            request()->request->set('view', $view);
        } elseif ($view === null) {
            $view = static::getDefault();
        }

        $selectedStatusIds = $view === 'incomplete'
            ? collect()
            : collect($parameters->get($this->key()))
                ->map(function ($value) {
                    return intval($value);
                })
                ->filter()
                ->values();

        $query->when($view === 'incomplete', function ($query) {
            $query->whereNull('ordered_at');
        })->when($view === 'outstanding' || $view === 'new', function ($query) use ($selectedStatusIds) {
            $outstandingStatusIds = \Aero\Cart\Models\OrderStatus::forState(...setting('admin.outstanding_order_states'))->pluck('id');

            $statusIds = $selectedStatusIds->isNotEmpty()
                ? $outstandingStatusIds->intersect($selectedStatusIds)
                : $outstandingStatusIds;

            if ($statusIds->isNotEmpty()) {
                $query->whereIn('order_status_id', $statusIds);
            }
        })->when($view === 'all', function ($query) use ($selectedStatusIds) {
            $query->whereNotNull('ordered_at');

            if ($selectedStatusIds->isNotEmpty()) {
                $query->whereIn('order_status_id', $selectedStatusIds);
            }
        });
    }

    protected function viewData(): array
    {
        $selectedStatuses = collect(request()->input($this->key()));
        $selectedView = request()->input('view', static::getDefault());

        $orderStatuses = \Aero\Cart\Models\OrderStatus::ordered()->get()
            ->map(function ($status) use ($selectedStatuses) {
                return [
                    'id' => $status->id,
                    'name' => $status->name,
                    'state' => $status->state,
                    'url' => $this->getUrlFor($status->id),
                ];
            });

        return [
            'selectedStatuses' => $selectedStatuses,
            'statuses' => collect($orderStatuses)
                ->filter(function ($status) use ($selectedView) {
                    switch ($selectedView) {
                        case 'all': return $status['state'] !== 'incomplete';
                        case 'new':
                        case 'outstanding': return in_array($status['state'], setting('admin.outstanding_order_states')->toArray());
                    }

                    return false;
                })
                ->toArray(),
        ];
    }

    protected function baseName(): string
    {
        return 'Status';
    }

    public function stateFields(): array
    {
        return [$this->key(), 'view'];
    }

    protected static function defaultValue(): string
    {
        return 'outstanding';
    }
}

```

And then create a view for rendering the filter, in this case `admin::filters.order-status`:

```php
<div class="border-b border-grey-light" data-filter-id="status">
    <button class="flex justify-between items-center w-full p-4" type="button" onClick="toggleContent(this)">
        <span class="text-sm font-medium">{{ $title }}</span>
        <span class="[ js-accordion-arrow ] w-4 h-4 flex items-center justify-center transform rotate-90">@include('admin::icons.caret-down')</span>
    </button>

    <div class="p-4 pt-2 list-reset [ js-accordion-content ] hidden">
        <div class="select w-full -mt-1">
            <select id="view_all" name="view" class="w-full" autocomplete="off" onchange="updateUrlQuery('view', this.value)">
                <option value="outstanding" {{ request('view', \Aero\Admin\Filters\Order\OrderStatusAdminFilter::getDefault()) === 'outstanding' || request('view', \Aero\Admin\Filters\Order\OrderStatusAdminFilter::getDefault()) === 'new' ? 'selected' : '' }} data-class="new">Outstanding Orders</option>
                <option value="all" data-class="all"
                        @if(request('view', \Aero\Admin\Filters\Order\OrderStatusAdminFilter::getDefault()) == 'all') selected data-filter-active="true" data-filter-group-name="View" data-filter-name="All Orders" data-filter-key="view" @endif>All Orders</option>
                <option value="incomplete" data-class="incomplete"
                        @if(request('view', \Aero\Admin\Filters\Order\OrderStatusAdminFilter::getDefault()) == 'incomplete') selected data-filter-active="true" data-filter-group-name="View" data-filter-name="Incomplete Orders" data-filter-key="view" @endif>Incomplete Orders</option>
            </select>
        </div>

        @if(count($statuses) > 0)
            <div class="pt-4 flex flex-col bg-white mt-2 overflow-hidden">
                <ul class="list-reset overflow-auto leading-loose" style="max-height: 216px">
                    @foreach($statuses as $status)
                        <li class="mb-2 filter">
                            <label for="status-{{ $status['id'] }}"><label class="checkbox">
                                    <input id="status-{{ $status['id'] }}" type="checkbox" name="{{ $key }}[]" autocomplete="off" value="{{ $status['id'] }}" onclick="location.href = '{{ $status['url'] ?? '' }}';"
                                           @if($selectedStatuses->contains($status['id'])) checked data-filter-active="true" data-filter-group-name="{{ $title }}" data-filter-name="{{ $status['name'] }}" data-filter-url="{{ $status['url'] }}" @endif
                                    > <span></span></label>
                                {{ $status['name'] }}
                            </label>
                        </li>
                    @endforeach
                </ul>
            </div>
        @endif
    </div>
</div>

```

Which receives the `viewData` defined by your filter type as well as the `options` defined by the base class (`$title` and `$key`).