Advanced Filters for Laravel

laratribe/laravel-advanced-filters v1.0.0 · 80 products · a live demo of laratribe/laravel-advanced-filters

Declare filters once on an Eloquent model. The package publishes what can be filtered and how, so the same definition drives a filter UI in Blade, Livewire or Inertia — or no UI at all behind a JSON API.

$ composer require laratribe/laravel-advanced-filters

This is the whole setup

Every page in this demo is powered by this one method. Nothing else declares a filter.

The filter definition 1 file
app/Models/Product.php the only place filters are declared
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laratribe\AdvancedFilters\Concerns\HasFilters;
use Laratribe\AdvancedFilters\Contracts\Filterable;
use Laratribe\AdvancedFilters\Filters\{DateFilter, NumericFilter, SetFilter, TextFilter};

class Product extends Model implements Filterable
{
    use HasFilters;

    protected $guarded = [];

    public $timestamps = false;

    protected function casts(): array
    {
        return ['price' => 'float', 'stock' => 'integer', 'released_at' => 'date'];
    }

    /**
     * The one place that decides what can be filtered — and therefore the allow-list.
     * Anything not declared here can never reach the query, whatever the request says.
     */
    public static function filters(): array
    {
        return [
            TextFilter::make('name', 'Name'),
            TextFilter::make('sku', 'SKU')->nullable(),
            SetFilter::make('category', 'Category')
                ->options([
                    'electronics' => 'Electronics',
                    'books' => 'Books',
                    'clothing' => 'Clothing',
                    'toys' => 'Toys',
                ])
                ->multiple(),
            SetFilter::make('status', 'Status')->options([
                'in_stock' => 'In stock',
                'low' => 'Low stock',
                'out' => 'Out of stock',
            ]),
            NumericFilter::make('price', 'Price'),
            NumericFilter::make('stock', 'Stock'),
            DateFilter::make('released_at', 'Released'),
        ];
    }
}

Three frontends, one definition

Each page below filters the same 80 products through the same model. Open “The code behind this page” on any of them — the model never changes.

Blade + Alpine

The packaged panel with no build step. Alpine from a CDN, package CSS and JS published to public/. Applying a filter reloads with the rows in the query string.

Open demo →

Livewire

No page reload. The panel owns the filter rows and dispatches an event; a separate results component listens and re-queries. Neither knows about the other.

Open demo →

JSON API

No Blade, no Alpine, no Livewire — just the wire contract. For Inertia, a SPA or a mobile client, where you render the interface yourself.

Open demo →

What this demo shows

Text, set, number and date filters, including a nullable SKU so is_empty has something to find.
Category is multiple() and status is not — so you can see the OR multi-select next to a single select.
Filters survive paging, because the active rows travel in the query string.
Undeclared columns are dropped. Try ?column_filters[0][field]=secret — the filter engine ignores it rather than erroring.