Skip to content

JSON API (no UI) ​

Steps 1 and 2 of the quick start have no frontend dependency — no Blade, no Alpine, no Livewire. For a SPA, a mobile client, or any UI you render yourself, use the package as a filter engine and talk to it through the wire contract.

Try the endpoints

advanced-filters.laratribe.com/api has both routes live against real data — including /api/products/filters, so you can see the field definitions this page describes before writing any code.

Two endpoints are usually enough:

php
use Laratribe\AdvancedFilters\Support\FilteredQuery;

// GET /api/products/filters — the column list, for building your UI
public function filters()
{
    return response()->json(['fields' => Product::filterDefinitions()]);
}

// GET|POST /api/products — filtered, paginated results
public function index(Request $request)
{
    return response()->json(
        FilteredQuery::for(Product::class)->fromRequest($request)->paginate(25)
    );
}

Request format ​

fromRequest() reads the configured query_key from wherever $request->input() finds it — query string or JSON body. Both of these work identically:

GET /api/products?column_filters[0][field]=status&column_filters[0][operator]=in&column_filters[0][value][0]=active
jsonc
// POST /api/products
{ "column_filters": [ { "field": "price", "operator": "greater_than", "value": 20 } ] }

Laravel resolves the input source by content type before it looks at the verb, so any method with Content-Type: application/json works — including the HTTP QUERY method once your router can dispatch it. Nothing in the package inspects the HTTP method.

Response ​

The paginator serialises straight to JSON:

jsonc
{ "data": [...], "current_page": 1, "total": 42, "per_page": 25, "last_page": 2 }

activeFilters() returns the normalised rows — echo those into your UI's chips so they reflect what was actually applied.

You don't need to validate the input ​

Every row goes through the same engine the Blade panel uses. A field the model doesn't declare, or an operator that field doesn't allow, is dropped before it reaches SQL rather than raising an error. filters() is the allow-list, so a client-supplied column name can never reach the query.

php
// 'password' is not declared in filters() — silently dropped
['field' => 'password', 'operator' => 'contains', 'value' => 'x']

Adding your own validation layer on top is redundant. If you'd rather reject bad input loudly than ignore it, compare count($request->input('column_filters')) against count($query->activeFilters()) and 422 on a mismatch.

Released under the MIT License.