PHPackages                             chr15k/laravel-meilisearch-advanced-query - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. chr15k/laravel-meilisearch-advanced-query

ActiveLibrary[Search &amp; Filtering](/categories/search)

chr15k/laravel-meilisearch-advanced-query
=========================================

A fluent query builder for Meilisearch filter expressions in Laravel.

3.0.2(1w ago)653.5k↓62.2%3MITPHPPHP ^8.3CI passing

Since Nov 8Pushed 1w ago1 watchersCompare

[ Source](https://github.com/chr15k/laravel-meilisearch-advanced-query)[ Packagist](https://packagist.org/packages/chr15k/laravel-meilisearch-advanced-query)[ RSS](/packages/chr15k-laravel-meilisearch-advanced-query/feed)WikiDiscussions main Synced 3d ago

READMEChangelog (10)Dependencies (18)Versions (15)Used By (0)

  ![Logo for Meilisearch Advanced Query package](art/header-light.svg) [![GitHub Workflow Status (master)](https://camo.githubusercontent.com/6f512731c66fd7cafcacc8d8818f3943873a65957c01eccba30c1b818ef9828f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f63687231356b2f6c61726176656c2d6d65696c697365617263682d616476616e6365642d71756572792f6d61696e2e796d6c)](https://github.com/chr15k/laravel-meilisearch-advanced-query/actions) [![Total Downloads](https://camo.githubusercontent.com/84b318bd38d8506ac9cae7441ae0cf9a6a2cf1295d8e5dbde57d6581489d13d5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f63687231356b2f6c61726176656c2d6d65696c697365617263682d616476616e6365642d7175657279)](https://packagist.org/packages/chr15k/laravel-meilisearch-advanced-query) [![Latest Version](https://camo.githubusercontent.com/2fcbecd2ee47e6c29c5539e61d8ff7d9eb7c0128de7f673b06459f0b3f69fac0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f63687231356b2f6c61726176656c2d6d65696c697365617263682d616476616e6365642d7175657279)](https://packagist.org/packages/chr15k/laravel-meilisearch-advanced-query) [![License](https://camo.githubusercontent.com/9ffc19b2f9d5c8f5a4bd4bd06fb6c1a044ea31407368bbe44897d0172b35ff30/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f63687231356b2f6c61726176656c2d6d65696c697365617263682d616476616e6365642d7175657279)](https://packagist.org/packages/chr15k/laravel-meilisearch-advanced-query)

---

**Meilisearch Advanced Query** provides a fluent, expressive API for building Meilisearch filter expressions in Laravel — the same filters you'd otherwise write by hand when [customising Scout engine searches](https://laravel.com/docs/13.x/scout#customizing-engine-searches).

It handles compound conditions, nested groups, range queries, geo filters, and Meilisearch-specific operators, keeping your code readable as queries grow in complexity.

Warning

v3 contains breaking changes. If you are upgrading from v2, see the [upgrade guide](UPGRADE.md).

---

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 12 or 13
- Laravel Scout 11+
- Meilisearch PHP SDK 1.16+

---

Installation
------------

[](#installation)

```
composer require chr15k/laravel-meilisearch-advanced-query
```

The service provider is registered automatically via Laravel's package discovery.

---

Usage
-----

[](#usage)

### Building a filter string

[](#building-a-filter-string)

Use the `Query` facade (or resolve `MeilisearchAdvancedQuery` from the container directly) to build a filter string without touching Scout at all:

```
use Chr15k\MeilisearchAdvancedQuery\Facades\Query;
use Chr15k\MeilisearchAdvancedQuery\Enums\Operator;

$filter = Query::where('status', Operator::EQ, 'active')
    ->whereIn('role', ['admin', 'editor'])
    ->whereBetween('login_count', 10, 500)
    ->compile();

// "status = 'active' AND role IN ['admin', 'editor'] AND login_count 10 TO 500"
```

### Running a Scout search

[](#running-a-scout-search)

Chain `forModel()` on the query builder to hand off to Scout. This returns a Scout `Builder` instance that you can continue to chain as normal:

```
use Chr15k\MeilisearchAdvancedQuery\Facades\Query;
use Chr15k\MeilisearchAdvancedQuery\Enums\Operator;
use App\Models\Product;

$results = Query::where('status', Operator::EQ, 'active')
    ->whereIn('category', ['boots', 'shoes'])
    ->forModel(Product::class)
    ->search('leather')
    ->paginate(20);
```

### Sorting

[](#sorting)

Pass a sort expression (or array of expressions) to `search()`:

```
Query::where('status', Operator::EQ, 'active')
    ->forModel(Product::class)
    ->search('leather', sort: ['price:asc', 'name:desc']);
```

For Meilisearch's sort syntax, see the [sorting documentation](https://www.meilisearch.com/docs/learn/filtering_and_sorting/sort_search_results).

---

The `Query` Facade
------------------

[](#the-query-facade)

The `Query` facade proxies to a fresh `MeilisearchAdvancedQuery` instance on each call, so there is no shared state between requests.

```
use Chr15k\MeilisearchAdvancedQuery\Facades\Query;
```

You can also resolve the builder from the container directly if you prefer:

```
use Chr15k\MeilisearchAdvancedQuery\MeilisearchAdvancedQuery;

$query = app(MeilisearchAdvancedQuery::class);
```

---

Builder Methods
---------------

[](#builder-methods)

### `where(field, operator, value, boolean)`

[](#wherefield-operator-value-boolean)

The primary method. Operator defaults to `Operator::EQ`. Boolean defaults to `AND`.

```
Query::where('name', Operator::EQ, 'Chris')->compile();
// "name = 'Chris'"

Query::where('count', Operator::GTE, 10)->compile();
// "count >= 10"

Query::where('verified', Operator::EQ, true)->compile();
// "verified = true"
```

### `orWhere(field, operator, value)`

[](#orwherefield-operator-value)

Identical to `where()` but joins with `OR`.

```
Query::where('name', Operator::EQ, 'Chris')
    ->orWhere('name', Operator::EQ, 'Bob')
    ->compile();
// "name = 'Chris' OR name = 'Bob'"
```

### `whereNot(field, value)` / `orWhereNot(field, value)`

[](#wherenotfield-value--orwherenotfield-value)

Negates a field equality check.

```
Query::whereNot('name', 'Chris')->compile();
// "NOT name = 'Chris'"

Query::where('verified', Operator::EQ, true)
    ->orWhereNot('name', 'Chris')
    ->compile();
// "verified = true OR NOT name = 'Chris'"
```

### `whereIn(field, values)` / `orWhereIn(field, values)`

[](#whereinfield-values--orwhereinfield-values)

Matches any value in the given array.

```
Query::whereIn('role', ['admin', 'editor'])->compile();
// "role IN ['admin', 'editor']"

Query::where('verified', Operator::EQ, true)
    ->orWhereIn('role', ['admin', 'editor'])
    ->compile();
// "verified = true OR role IN ['admin', 'editor']"
```

### `whereNotIn(field, values)` / `orWhereNotIn(field, values)`

[](#wherenotinfield-values--orwherenotinfield-values)

Excludes any value in the given array.

```
Query::whereNotIn('status', ['banned', 'suspended'])->compile();
// "status NOT IN ['banned', 'suspended']"
```

### `whereBetween(field, from, to)` / `orWhereBetween(field, from, to)`

[](#wherebetweenfield-from-to--orwherebetweenfield-from-to)

Range filter using Meilisearch's `TO` operator.

```
Query::whereBetween('price', 10, 100)->compile();
// "price 10 TO 100"
```

### `whereExists(field)` / `orWhereExists(field)`

[](#whereexistsfield--orwhereexistsfield)

Matches documents where the field exists.

```
Query::whereExists('verified_at')->compile();
// "verified_at EXISTS"
```

### `whereIsNull(field)` / `orWhereIsNull(field)`

[](#whereisnullfield--orwhereisnullfield)

Matches documents where the field is `null`.

```
Query::whereIsNull('deleted_at')->compile();
// "deleted_at IS NULL"
```

### `whereIsEmpty(field)` / `orWhereIsEmpty(field)`

[](#whereisemptyfield--orwhereisemptyfield)

Matches documents where the field is empty.

```
Query::whereIsEmpty('tags')->compile();
// "tags IS EMPTY"
```

### `whereRaw(query)` / `orWhereRaw(query)`

[](#whererawquery--orwhererawquery)

Passes a raw filter string through the compiler unchanged. Useful for filter expressions the builder does not yet support natively.

```
Query::whereRaw("name = 'Chris' OR name = 'Bob'")->compile();
// "name = 'Chris' OR name = 'Bob'"

Query::whereRaw("name = 'Chris'")
    ->orWhereRaw("name = 'Bob'")
    ->compile();
// "name = 'Chris' OR name = 'Bob'"
```

### Geo filters

[](#geo-filters)

#### `whereGeoRadius(lat, lng, distanceInMeters)` / `orWhereGeoRadius`

[](#wheregeoradiuslat-lng-distanceinmeters--orwheregeoradius)

```
Query::where('active', Operator::EQ, true)
    ->whereGeoRadius(48.8566, 2.3522, 1000)
    ->compile();
// "active = true AND _geoRadius(48.8566, 2.3522, 1000)"
```

#### `whereGeoBoundingBox(lat1, lng1, lat2, lng2)` / `orWhereGeoBoundingBox`

[](#wheregeoboundingboxlat1-lng1-lat2-lng2--orwheregeoboundingbox)

```
Query::where('active', Operator::EQ, true)
    ->whereGeoBoundingBox(48.8566, 2.3522, 48.9, 2.4)
    ->compile();
// "active = true AND _geoBoundingBox([48.8566, 2.3522], [48.9, 2.4])"
```

---

Nested / Grouped Queries
------------------------

[](#nested--grouped-queries)

Pass a closure to `where()` or `orWhere()` to create a parenthesised group:

```
Query::where(fn ($q) => $q
    ->where('name', Operator::EQ, 'Chris')
    ->orWhere('name', Operator::EQ, 'Bob')
)
->where('verified', Operator::EQ, true)
->compile();
// "(name = 'Chris' OR name = 'Bob') AND verified = true"
```

Groups can be nested to any depth:

```
Query::where(fn ($q) => $q
    ->where('count', Operator::GTE, 10)
    ->where('count', Operator::LTE, 100)
    ->orWhere(fn ($sub) => $sub
        ->where('name', Operator::EQ, 'Chris')
        ->orWhereIsEmpty('name')
        ->orWhereIsNull('email')
    )
)
->orWhere('name', Operator::EQ, 'Bob')
->compile();
// "(count >= 10 AND count
