PHPackages                             justraviga/laravel-elastic-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. justraviga/laravel-elastic-query

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

justraviga/laravel-elastic-query
================================

02PHP

Since Mar 13Pushed 3y agoCompare

[ Source](https://github.com/JustRaviga/laravel-elastic-query)[ Packagist](https://packagist.org/packages/justraviga/laravel-elastic-query)[ RSS](/packages/justraviga-laravel-elastic-query/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

Laravel Elastic Query
=====================

[](#laravel-elastic-query)

Working with Elasticsearch in an Eloquent-like fashion.

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

[](#installation)

You can install the package via composer:

1. `composer require ensi/laravel-elastic-query`
2. Set `ELASTICSEARCH_HOSTS` in your `.env` file. `,` can be used as a delimeter.

Basic usage
-----------

[](#basic-usage)

Let's create and index class. It's someting like Eloquent model.

```
use Ensi\LaravelElasticQuery\ElasticIndex;

class ProductsIndex extends ElasticIndex
{
    protected string $name = 'test_products';
    protected string $tiebreaker = 'product_id';
}
```

You should set a unique in document attribute name in `$tiebreaker`. It is used as an additional sort in `search_after`

Now we can get some documents

```
$searchQuery = ProductsIndex::query();

$hits = $searchQuery
             ->where('rating', '>=', 5)
             ->whereDoesntHave('offers', fn(BoolQuery $query) => $query->where('seller_id', 10)->where('active', false))
             ->sortBy('rating', 'desc')
             ->sortByNested('offers', fn(SortableQuery $query) => $query->where('active', true)->sortBy('price', mode: 'min'))
             ->take(25)
             ->get();
```

### Filtering

[](#filtering)

```
$searchQuery->where('field', 'value');
$searchQuery->where('field', '>', 'value'); // supported operators: `=` `!=` `>` `=` `whereNot('field', 'value'); // equals `where('field', '!=', 'value')`
```

```
$searchQuery->whereIn('field', ['value1', 'value2']);
$searchQuery->whereNotIn('field', ['value1', 'value2']);
```

```
$searchQuery->whereNull('field');
$searchQuery->whereNotNull('field');
```

```
$searchQuery->whereHas('nested_field', fn(BoolQuery $subQuery) => $subQuery->where('field_in_nested', 'value'));
$searchQuery->whereDoesntHave(
    'nested_field',
    function (BoolQuery $subQuery) {
        $subQuery->whereHas('nested_field', fn(BoolQuery $subQuery2) => $subQuery2->whereNot('field', 'value'));
    }
);
```

`nested_field` must have `nested` type. Subqueries cannot use fields of main document only subdocument.

### Full text search

[](#full-text-search)

```
$searchQuery->whereMatch('field_one', 'query string');
$searchQuery->whereMultiMatch(['field_one^3', 'field_two'], 'query string', MatchType::MOST_FIELDS);
$searchQuery->whereMultiMatch([], 'query string');  // search by all text fields
```

`field_one` and `field_two` must be of text type. If no type is given, the `MatchType::BEST_FIELDS` is used.

### Sorting

[](#sorting)

```
$searchQuery->sortBy('field', SortOrder::DESC, SortMode::MAX, MissingValuesMode::FIRST); // field is from main document
$searchQuery->sortByNested(
    'nested_field',
    fn(SortableQuery $subQuery) => $subQuery->where('field_in_nested', 'value')->sortBy('field')
);
```

Second attribute is a direction. It supports `asc` and `desc` values. Defaults to `asc`.
Third attribute - sorting type. List of supporting types: `min, max, avg, sum, median`. Defaults to `min`.

There are also dedicated sort methods for each sort type.

```
$searchQuery->minSortBy('field', 'asc');
$searchQuery->maxSortBy('field', 'asc');
$searchQuery->avgSortBy('field', 'asc');
$searchQuery->sumSortBy('field', 'asc');
$searchQuery->medianSortBy('field', 'asc');
```

### Pagination

[](#pagination)

#### Offset Pagination

[](#offset-pagination)

```
$page = $searchQuery->paginate(15, 45);
```

Offset pagination returns total documents count as `total` and current position as `size/offset`.

#### Cursor pagination

[](#cursor-pagination)

```
$page = $searchQuery->cursorPaginate(10);
$pageNext = $searchQuery->cursorPaginate(10, $page->next);
```

`current`, `next`, `previous` is returned in this case instead of `total`, `size` and `offset`. You can check Laravel docs for more info about cursor pagination.

Aggregation
-----------

[](#aggregation)

Aggregaction queries can be created like this

```
$aggQuery = ProductsIndex::aggregate();

/** @var \Illuminate\Support\Collection $aggs */
$aggs = $aggQuery
            ->where('active', true)
            ->terms('codes', 'code')
            ->count('product_count', 'product_id')
            ->nested(
                'offers',
                fn(AggregationsBuilder $builder) => $builder->where('seller_id', 10)->minmax('price', 'price')
            );

```

Type of `$aggs->price` is `MinMax`. Type of `$aggs->codes` is `BucketCollection`. Aggregate names must be unique for whole query.

### Aggregate types

[](#aggregate-types)

Get all variants of attribute values:

```
$aggQuery->terms('agg_name', 'field', 25);
```

Get min and max attribute values. E.g for date:

```
$aggQuery->minmax('agg_name', 'field');
```

Get count unique attribute values:

```
$aggQuery->count('agg_name', 'field');
```

Aggregation plays nice with nested documents.

```
$aggQuery->nested('nested_field', function (AggregationsBuilder $builder) {
    $builder->terms('name', 'field_in_nested');
});
```

There is also a special virtual `composite` aggregate on the root level. You can set special conditions using it.

```
$aggQuery->composite(function (AggregationsBuilder $builder) {
    $builder->where('field', 'value')
        ->whereHas('nested_field', fn(BoolQuery $query) => $query->where('field_in_nested', 'value2'))
        ->terms('field1', 'agg_name1')
        ->minmax('field2', 'agg_name2');
});
```

Suggesting
----------

[](#suggesting)

Suggest queries can be created like this

```
$sugQuery = ProductsIndex::suggest();

/** @var \Illuminate\Support\Collection $suggests */
$suggests = $sugQuery->phrase('suggestName', 'name.trigram')
    ->text('glves')
    ->size(1)
    ->shardSize(3)
    ->get();

```

### Global suggest text

[](#global-suggest-text)

User can set global text like this

```
$sugQuery = ProductsIndex::suggest()->text('glves');

$sugQuery->phrase('suggestName1', 'name.trigram')->size(1)->shardSize(3);

$sugQuery->phrase('suggestName2', 'name.trigram');

/** @var \Illuminate\Support\Collection $suggests */
$suggests = $sugQuery->get();

```

### Suggester types

[](#suggester-types)

Term suggester:

```
$aggQuery->term('suggestName', 'name.trigram')->text('glves')->...->get();
```

Phrase Suggester:

```
$aggQuery->phrase('suggestName', 'name.trigram')->text('glves')->...->get();
```

Additional methods
------------------

[](#additional-methods)

```
$index = new ProductsIndex();

$index->isCreated(); // Check if index are created
$index->create(); // Create index with structure from settings() method
$index->bulk(); // Send bulk request
$index->get(); // Send get request
$index->documentDelete(); // Send documentDelete request
$index->deleteByQuery(); // Send deleteByQuery request

$index->catIndices();
$index->indicesDelete();
$index->indicesRefresh();
```

Query Log
---------

[](#query-log)

Just like Eloquent ElasticQuery has its own query log, but you need to enable it manually Each message contains `indexName`, `query` and `timestamp`

```
ElasticQuery::enableQueryLog();

/** @var \Illuminate\Support\Collection|Ensi\LaravelElasticQuery\Debug\QueryLogRecord[] $records */
$records = ElasticQuery::getQueryLog();

ElasticQuery::disableQueryLog();
```

Environment Variables
---------------------

[](#environment-variables)

Below see the environment variables that you can configure with the default values, Hosts should be comma seperated string of hosts with protocol prefix and port suffix, e.g. `http://localhost:9200,http://localhost:9201`

```
 ELASTICSEARCH_HOSTS=https://localhost:9200'
 ELASTICSEARCH_RETRIES=2
 ELASTICSEARCH_USERNAME=admin
 ELASTICSEARCH_PASSWORD=admin
 ELASTICSEARCH_SSL_VERIFICATION=true,
```

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.

### Testing

[](#testing)

1. composer install
2. npm i
3. Start Elasticsearch in your preferred way.
4. Copy `phpunit.xml.dist` to `phpunit.xml` and set correct env variables there
5. composer test

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

14

—

LowBetter than 2% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity2

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity23

Early-stage or recently created project

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/b8ca566de4400c8eead7ed9debed897d5b99d2231b7a1543063c999d5c5ee9ba?d=identicon)[JustRaviga](/maintainers/JustRaviga)

---

Top Contributors

[![zix2](https://avatars.githubusercontent.com/u/51262118?v=4)](https://github.com/zix2 "zix2 (14 commits)")[![arrilot](https://avatars.githubusercontent.com/u/2826480?v=4)](https://github.com/arrilot "arrilot (13 commits)")[![MsNatali](https://avatars.githubusercontent.com/u/8089373?v=4)](https://github.com/MsNatali "MsNatali (4 commits)")[![MPJHorner](https://avatars.githubusercontent.com/u/4990716?v=4)](https://github.com/MPJHorner "MPJHorner (3 commits)")[![DimionX](https://avatars.githubusercontent.com/u/7352966?v=4)](https://github.com/DimionX "DimionX (2 commits)")[![koopaTro0pa](https://avatars.githubusercontent.com/u/60116849?v=4)](https://github.com/koopaTro0pa "koopaTro0pa (1 commits)")[![SadDone](https://avatars.githubusercontent.com/u/47665715?v=4)](https://github.com/SadDone "SadDone (1 commits)")

### Embed Badge

![Health badge](/badges/justraviga-laravel-elastic-query/health.svg)

```
[![Health](https://phpackages.com/badges/justraviga-laravel-elastic-query/health.svg)](https://phpackages.com/packages/justraviga-laravel-elastic-query)
```

###  Alternatives

[ruflin/elastica

Elasticsearch Client

2.3k50.4M203](/packages/ruflin-elastica)[opensearch-project/opensearch-php

PHP Client for OpenSearch

15224.3M65](/packages/opensearch-project-opensearch-php)[mailerlite/laravel-elasticsearch

An easy way to use the official PHP ElasticSearch client in your Laravel applications.

934529.3k2](/packages/mailerlite-laravel-elasticsearch)[massive/search-bundle

Massive Search Bundle

721.4M13](/packages/massive-search-bundle)[shyim/opensearch-php-dsl

OpenSearch/Elasticsearch DSL library

175.9M9](/packages/shyim-opensearch-php-dsl)[outl1ne/nova-multiselect-filter

Multiselect filter for Laravel Nova.

45802.7k3](/packages/outl1ne-nova-multiselect-filter)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
