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