Filters

What filter types are available?
As of 
15/09/2026, there are eleven filter types available:
Type
Description
Invertible?
Dropdown
Allows users to select a single option from a dropdown menu.
Yes
Radio List
Allows users to select exactly one option from a list of radio buttons.
Yes
Checkbox List
Allows users to select one or more options from a list of checkboxes.
Yes
Min Max
Allows users to specify minimum and maximum values for a numeric range.
No
Min Max Price
Allows users to specify minimum and maximum values for a price range.
No
Date Range
Allows users to filter results by selecting a start date and end date.
No
Options Date Range
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.
No
Searchable Select
Allows users to search for and select an option from a searchable list.
Yes
Searchable Select When Over Count
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.
Yes
Searchable Select With Modal
A Searchable Select Filter that supports searching via modal.
Yes
Searchable Select With Product Picker
A Searchable Select Filter for products that supports searching via product picker.
Yes
Dropdown Filter
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
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
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';
    }
}
getUrlFor is a helper method provided by the 
Aero\Admin\Filters\Traits\AdminFilterHelpers trait.
Min Max Filter
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
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
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
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
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
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
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
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:
Type
Handle Method
Inverted Handle Method
Dropdown
handleDropdown
handleInvertedDropdown
Radio List
handleRadioList
handleInvertedRadioList
Checkbox List
handleCheckboxList
handleInvertedCheckboxList
Searchable Select
handleSearchableSelect
handleInvertedSearchableSelect
Searchable Select When Over Count
handleSearchableSelectWhenOverCount
handleInvertedSearchableSelectWhenOverCount
Searchable Select With Modal
handleSearchableSelect
handleInvertedSearchableSelect
Searchable Select With Product Picker
handleSearchableSelect
handleInvertedSearchableSelect
To enable inverted handling, set 
$inverted to 
true on the filter:
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';
    }
}
The inverted handler is optional for Laravel 10+ (in most cases), as it is automatically generated using 
whereNot.

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:
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:
<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).