laratribe/laravel-advanced-filters v1.0.0 · 80 products ·
no Blade, no Alpine, no Livewire — just the wire contract
The same engine with the UI removed. Useful for Inertia, a SPA, or a mobile client — you render the interface, the package validates and applies the filters.
Try it:
A JSON body works too — $request->input() reads either source:
curl -X POST https://advanced-filters.laratribe.com/api/products \
-H 'Content-Type: application/json' \
-d '{"column_filters":[{"field":"stock","operator":"between","value":10,"valueTo":40}]}'
Same model again, with the UI removed entirely. Two routes is the whole integration — no Blade, no Alpine, no Livewire.
app/Models/Product.php
identical on all three pages <?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'),
];
}
}
routes/web.php
the /api routes at the bottom <?php
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Laratribe\AdvancedFilters\Support\FilteredQuery;
Route::get('/', fn () => view('home'))->name('demo.home');
// ── Blade + Alpine ───────────────────────────────────────────────────────────
Route::get('/blade', function (Request $request) {
$query = FilteredQuery::for(Product::class)->fromRequest($request);
return view('blade', [
'products' => $query->paginate(15),
'fields' => $query->filterDefinitions(), // wire contract OUT
'active' => $query->activeFilters(), // normalised rows, for the chips
]);
})->name('demo.blade');
// ── Livewire ─────────────────────────────────────────────────────────────────
Route::get('/livewire', fn () => view('livewire-page'))->name('demo.livewire');
// ── JSON API (no UI at all) ──────────────────────────────────────────────────
Route::get('/api', fn () => view('api'))->name('demo.api');
Route::get('/api/products/filters', fn () => response()->json([
'fields' => Product::filterDefinitions(),
]))->name('api.filters');
Route::match(['get', 'post'], '/api/products', fn (Request $request) => response()->json(
FilteredQuery::for(Product::class)->fromRequest($request)->paginate(15)
))->name('api.products');