Skip to content

Filter Groups

Spatie laravel-query-builder v7.3.0 / PR #1060 — JSON:API Fancy Filter compositions with explicit AND/OR conjunctions, surfaced as a single shorthand parameter on the client.

Why this matters

Before Filter Groups, expressing "search across name OR full_name" required either:

  • A custom filter on the server (boilerplate per-search-field combination), or
  • An operator-level OR smuggled into a generic q string parser (fragile, easy to leak SQL precedence bugs).

Filter Groups make that compositions a first-class declaration on the server, with the client just sending the shorthand.

The contract

Server (Spatie ≥ 7.3.0):

php
use Spatie\QueryBuilder\AllowedFilter;

QueryBuilder::for(User::class)
    ->allowedFilters(
        AllowedFilter::groupOr('q', [
            AllowedFilter::partial('name'),
            AllowedFilter::partial('full_name'),
        ]),
    );

Client (crisp-oquent):

ts
await User.crispy().filterGroup('q', 'John').get();

Wire format:

http
GET /users?filter[q]=John

Resulting SQL:

sql
WHERE (LOWER(name) LIKE '%john%' OR LOWER(full_name) LIKE '%john%')

groupOr vs groupAnd

php
// All members must match — useful for combining several constraints under one shorthand.
AllowedFilter::groupAnd('strict_search', [
    AllowedFilter::partial('name'),
    AllowedFilter::exact('verified'),
]);

The client shorthand is identical:

ts
await User.crispy().filterGroup('strict_search', 'value').get();

The conjunction lives on the server. The client never has to know.

Composing with regular filters

Multiple groups and top-level filters AND-compose:

ts
await User.crispy()
  .filter('status', 'active')                  // top-level filter
  .filterGroup('q', 'John')                    // group OR
  .filterGroup('region_search', 'Istanbul')    // another group OR
  .get();

Wire:

http
GET /users?filter[status]=active&filter[q]=John&filter[region_search]=Istanbul

SQL precedence (server-side):

sql
WHERE status = 'active'
  AND (name LIKE '%John%' OR full_name LIKE '%John%')
  AND (city LIKE '%Istanbul%' OR region LIKE '%Istanbul%')

Outer-where(closure) wrappers around each group preserve precedence — see the #1060 PR description for the SQL-shape regression test.

Standards anchor — JSON:API Fancy Filters

The Filter Groups feature is anchored on the JSON:API Fancy Filters recommendation, which Drupal's JSON:API module ships in production.

The base JSON:API spec leaves the filter parameter family intentionally extension-friendly — Fancy Filters is the established standards anchor for groups / conditions with explicit AND/OR conjunctions.

Roadmap — full Fancy Filters URL syntax

The current Spatie implementation supports the shorthand form (?filter[q]=value). The full Fancy Filters URL syntax (?filter[g1][conjunction]=OR&filter[g1][conditions][...]) is listed as a follow-up in #1060's "Out of scope" section.

When it lands in a future Spatie minor, crisp-oquent will gain the corresponding builder DSL — likely under an opt-in flag until both sides agree on the wire format.