PHPackages                             sendynl/opensearch-query-builder - 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. sendynl/opensearch-query-builder

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

sendynl/opensearch-query-builder
================================

Build and execute an OpenSearch search query using a fluent PHP API

v1.0(11mo ago)13.5k↓50%MITPHPPHP ^8.2CI passing

Since Jun 6Pushed 11mo ago1 watchersCompare

[ Source](https://github.com/sendynl/opensearch-query-builder)[ Packagist](https://packagist.org/packages/sendynl/opensearch-query-builder)[ Docs](https://github.com/sendynl/opensearch-query-builder)[ RSS](/packages/sendynl-opensearch-query-builder/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (8)Versions (2)Used By (0)

Build and execute OpenSearch queries using a fluent PHP API
===========================================================

[](#build-and-execute-opensearch-queries-using-a-fluent-php-api)

[![Latest Version on Packagist](https://camo.githubusercontent.com/73eec208c98b71e6782469414ec2c54dd8f7c194988c5fdb68c57ffc166b71e7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73656e64796e6c2f6f70656e7365617263682d71756572792d6275696c6465722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/sendynl/opensearch-query-builder)[![Tests](https://github.com/sendynl/opensearch-query-builder/actions/workflows/run-tests.yml/badge.svg)](https://github.com/sendynl/opensearch-query-builder/actions/workflows/run-tests.yml)[![Total Downloads](https://camo.githubusercontent.com/d2faed16293c24e2239bb386a1bb2c56a2c9727373527f2fc4140edf47a3e6be/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f73656e64796e6c2f6f70656e7365617263682d71756572792d6275696c6465722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/sendynl/opensearch-query-builder)

---

This package is a *lightweight* query builder for OpenSearch. It is forked from the [spatie/elasticsearch-query-builder](https://github.com/spatie/elasticsearch-query-builder) library and modified to support OpenSearch. We're always open for PRs if you need anything specific!

```
use Sendy\OpenSearchQueryBuilder\Aggregations\MaxAggregation;
use Sendy\OpenSearchQueryBuilder\Builder;
use Sendy\OpenSearchQueryBuilder\Queries\MatchQuery;

$transport = (new \OpenSearch\TransportFactory())->create();

$client = new \OpenSearch\Client($transport);

$companies = (new Builder($client))
    ->index('companies')
    ->addQuery(MatchQuery::create('name', 'sendy', fuzziness: 3))
    ->addAggregation(MaxAggregation::create('score'))
    ->search();
```

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

[](#installation)

You can install the package via composer:

```
composer require sendynl/opensearch-query-builder
```

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

[](#basic-usage)

The only class you really need to interact with is the `Sendy\OpenSearchQueryBuilder\Builder` class. It requires an `\OpenSearch\Client` passed in the constructor. Take a look at the [OpenSearch SDK docs](https://docs.opensearch.org/docs/latest/clients/php/) to learn more about connecting to your OpenSearch cluster.

The `Builder` class contains some methods to [add queries](#adding-queries), [aggregations](#adding-aggregations), [sorts](#adding-sorts), [fields](#retrieve-specific-fields) and some extras for [pagination](#pagination). You can read more about these methods below. Once you've fully built-up the query you can use `$builder->search()` to execute the query or `$builder->getPayload()` to get the raw payload for OpenSearch.

```
use Sendy\OpenSearchQueryBuilder\Queries\RangeQuery;
use Sendy\OpenSearchQueryBuilder\Builder;

$transport = (new \OpenSearch\TransportFactory())->create();

$client = new \OpenSearch\Client($transport);

$builder = new Builder($client);

$builder->addQuery(RangeQuery::create('age')->gte(18));

$results = $builder->search(); // raw response from OpenSearch
```

#### Multi-Search Queries

[](#multi-search-queries)

Multi-Search queries are also available using the [`MultiBuilder` class](#multi-search-query-builder).

Adding queries
--------------

[](#adding-queries)

The `$builder->addQuery()` method can be used to add any of the available `Query` types to the builder. The available query types can be found below or in the `src/Queries` directory of this repo. Every `Query` has a static `create()` method to pass its most important parameters.

The following query types are available:

#### `ExistsQuery`

[](#existsquery)

```
\Sendy\OpenSearchQueryBuilder\Queries\ExistsQuery::create('terms_and_conditions');
```

#### `GeoshapeQuery`

[](#geoshapequery)

```
\Sendy\OpenSearchQueryBuilder\Queries\GeoshapeQuery::create(
  'location',
  \Sendy\OpenSearchQueryBuilder\Queries\GeoshapeQuery::TYPE_POLYGON,
  [[1.0, 2.0]],
  \Sendy\OpenSearchQueryBuilder\Queries\GeoShapeQuery::RELATION_INTERSECTS,
);
```

#### `MatchQuery`

[](#matchquery)

```
\Sendy\OpenSearchQueryBuilder\Queries\MatchQuery::create('name', 'john doe', fuzziness: 2, boost: 5.0);
```

#### `MatchPhraseQuery`

[](#matchphrasequery)

```
\Sendy\OpenSearchQueryBuilder\Queries\MatchPhraseQuery::create('name', 'john doe', slop: 2,zeroTermsQuery: "none",analyzer: "my_analyzer");
```

#### `MultiMatchQuery`

[](#multimatchquery)

```
\Sendy\OpenSearchQueryBuilder\Queries\MultiMatchQuery::create('john', ['email', 'email'], fuzziness: 'auto');
```

#### `NestedQuery`

[](#nestedquery)

```
\Sendy\OpenSearchQueryBuilder\Queries\NestedQuery::create(
    'user',
    new \Sendy\OpenSearchQueryBuilder\Queries\MatchQuery('name', 'john')
);
```

##### `NestedQuery` `InnerHits`

[](#nestedquery-innerhits)

```
$nestedQuery = \Sendy\OpenSearchQueryBuilder\Queries\NestedQuery::create(
    'comments',
    \Sendy\OpenSearchQueryBuilder\Queries\TermsQuery::create('comments.published', true)
);

$nestedQuery->innerHits(
    \Sendy\OpenSearchQueryBuilder\Queries\NestedQuery\InnerHits::create('top_three_liked_comments')
        ->size(3)
        ->addSort(
            \Sendy\OpenSearchQueryBuilder\Sorts\Sort::create(
                'comments.likes',
                \Sendy\OpenSearchQueryBuilder\Sorts\Sort::DESC
            )
        )
        ->fields(['comments.content', 'comments.author', 'comments.likes'])
);
```

#### `RangeQuery`

[](#rangequery)

```
\Sendy\OpenSearchQueryBuilder\Queries\RangeQuery::create('age')
    ->gte(18)
    ->lte(1337);
```

#### `TermQuery`

[](#termquery)

```
\Sendy\OpenSearchQueryBuilder\Queries\TermQuery::create('user.id', 'flx');
```

#### `TermsQuery`

[](#termsquery)

```
\Sendy\OpenSearchQueryBuilder\Queries\TermsQuery::create('user.id', ['flx', 'fly'], boost: 5.0);
```

#### `WildcardQuery`

[](#wildcardquery)

```
\Sendy\OpenSearchQueryBuilder\Queries\WildcardQuery::create('user.id', '*doe');
```

#### `PercolateQuery`

[](#percolatequery)

```
\Sendy\OpenSearchQueryBuilder\Queries\PercolateQuery::create('query', ['title' => 'foo', 'body' => 'bar']);
```

#### `BoolQuery`

[](#boolquery)

```
\Sendy\OpenSearchQueryBuilder\Queries\BoolQuery::create()
    ->add($matchQuery, 'must_not')
    ->add($existsQuery, 'must_not');
```

#### `collapse`

[](#collapse)

The `collapse` feature allows grouping search results by a specific field while retrieving top documents from each group using `inner_hits`. This is useful for avoiding duplicate entities in search results while still accessing grouped data.

```
use Sendy\OpenSearchQueryBuilder\Sorts\Sort;
use Sendy\OpenSearchQueryBuilder\Builder;

// Initialize ExtendedBuilder with an OpenSearch client
$builder = new Builder($client);

// Apply collapse to group by 'user_id'
$builder->collapse(
    'user_id', // Field to collapse on
    [
        'name' => 'top_three_liked_posts',
        'size' => 3, // Retrieve top 3 posts per user
        'sort' => [
            Sort::create('post.likes', Sort::DESC), // Sort posts by likes (descending)
        ],
        'fields' => ['post.title', 'post.content', 'post.likes'], // Select specific fields
    ],
    10, // Max concurrent group searches
);

// Execute the search
$response = $builder->search();
```

### Chaining multiple queries

[](#chaining-multiple-queries)

Multiple `addQuery()` calls can be chained on one `Builder`. Under the hood they'll be added to a `BoolQuery` with occurrence type `must`. By passing a second argument to the `addQuery()` method you can select a different occurrence type:

```
$builder
    ->addQuery(
        MatchQuery::create('name', 'billie'),
        'must_not' // available types: must, must_not, should, filter
    )
    ->addQuery(
        MatchQuery::create('team', 'eillish')
    );
```

More information on the boolean query and its occurrence types can be found [in the OpenSearch docs](https://docs.opensearch.org/docs/latest/query-dsl/compound/bool/).

Adding aggregations
-------------------

[](#adding-aggregations)

The `$builder->addAggregation()` method can be used to add any of the available `Aggregation`s to the builder. The available aggregation types can be found below or in the `src/Aggregations` directory of this repo. Every `Aggregation` has a static `create()` method to pass its most important parameters and sometimes some extra methods.

```
use Sendy\OpenSearchQueryBuilder\Aggregations\TermsAggregation;
use Sendy\OpenSearchQueryBuilder\Builder;

$transport = (new \OpenSearch\TransportFactory())->create();

$client = new \OpenSearch\Client($transport);

$results = (new Builder($client))
    ->addAggregation(TermsAggregation::create('genres', 'genre'))
    ->search();

$genres = $results['aggregations']['genres']['buckets'];
```

The following query types are available:

#### `CardinalityAggregation`

[](#cardinalityaggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\CardinalityAggregation::create('team_agg', 'team_name');
```

#### `FilterAggregation`

[](#filteraggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\FilterAggregation::create(
    'tshirts',
    \Sendy\OpenSearchQueryBuilder\Queries\TermQuery::create('type', 'tshirt'),
    \Sendy\OpenSearchQueryBuilder\Aggregations\MaxAggregation::create('max_price', 'price')
);
```

#### `MaxAggregation`

[](#maxaggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\MaxAggregation::create('max_price', 'price');
```

#### `MinAggregation`

[](#minaggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\MinAggregation::create('min_price', 'price');
```

#### `SumAggregation`

[](#sumaggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\SumAggregation::create('sum_price', 'price');
```

#### `NestedAggregation`

[](#nestedaggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\NestedAggregation::create(
    'resellers',
    'resellers',
    \Sendy\OpenSearchQueryBuilder\Aggregations\MinAggregation::create('min_price', 'resellers.price'),
    \Sendy\OpenSearchQueryBuilder\Aggregations\MaxAggregation::create('max_price', 'resellers.price'),
);
```

#### `ReverseNestedAggregation`

[](#reversenestedaggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\ReverseNestedAggregation::create(
    'name',
    ...$aggregations
);
```

#### `TermsAggregation`

[](#termsaggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\TermsAggregation::create(
    'genres',
    'genre'
)
    ->size(10)
    ->order(['_count' => 'asc'])
    ->missing('N/A')
    ->aggregation(/* $subAggregation */);
```

#### `TopHitsAggregation`

[](#tophitsaggregation)

```
\Sendy\OpenSearchQueryBuilder\Aggregations\TopHitsAggregation::create(
    'top_sales_hits',
    size: 10,
);
```

#### `DateHistogramAggregation`

[](#datehistogramaggregation)

```
\Sendynl\OpenSearchQueryBuilder\Aggregations\DateHistogramAggregation::create(
    'name',
    '@timestamp'
    '1h',
)
    ->aggregation(/* $subAggregation */);
```

Adding sorts
------------

[](#adding-sorts)

The `Builder` (and some aggregations) has a `addSort()` method that takes a `Sort` instance to sort the results. You can read more about how sorting works in [the OpenSearch docs](https://docs.opensearch.org/docs/latest/search-plugins/searching-data/sort/).

```
use Sendy\OpenSearchQueryBuilder\Sorts\Sort;

$builder
    ->addSort(Sort::create('age', Sort::DESC))
    ->addSort(
        Sort::create('score', Sort::ASC)
            ->unmappedType('long')
            ->missing(0)
    );
```

### Nested sort

[](#nested-sort)

```
use Sendy\OpenSearchQueryBuilder\Sorts\NestedSort;

$builder
    ->addSort(
        NestedSort::create('books', 'books.rating', NestedSort::ASC)
    );
```

#### Nested sort with filter

[](#nested-sort-with-filter)

```
use Sendy\OpenSearchQueryBuilder\Sorts\NestedSort;
use Sendy\OpenSearchQueryBuilder\Queries\BoolQuery;
use Sendy\OpenSearchQueryBuilder\Queries\TermQuery;

$builder
    ->addSort(
        NestedSort::create(
            'books',
            'books.rating',
            NestedSort::ASC
        )->filter(BoolQuery::create()->add(TermQuery::create('books.category', 'comedy'))
    );
```

Retrieve specific fields
------------------------

[](#retrieve-specific-fields)

The `fields()` method can be used to request specific fields from the resulting documents without returning the entire `_source` entry. You can read more about the specifics of the fields parameter in [the OpenSearch docs](https://docs.opensearch.org/docs/latest/search-plugins/searching-data/retrieve-specific-fields/).

```
$builder->fields('user.id', 'http.*.status');
```

Highlighting
------------

[](#highlighting)

The `highlight()` method can be used to add a highlight section to your query along the rules in [the OpenSearch docs](https://docs.opensearch.org/docs/latest/search-plugins/searching-data/highlight/).

```
$highlightSettings = [
    'pre_tags' => [''],
    'post_tags' => [''],
    'fields' => [
        '*' => (object) []
    ]
];

$builder->highlight($highlightSettings);
```

Post filter
-----------

[](#post-filter)

The `addPostFilterQuery()` method can be used to add a post\_filter BoolQuery to your query along the rules in [the OpenSearch docs](https://docs.opensearch.org/docs/latest/search-plugins/filter-search/#narrowing-results-using-post-filter-while-preserving-aggregation-visibility).

```
use Sendy\OpenSearchQueryBuilder\Queries\TermsQuery;

$builder->addPostFilterQuery(TermsQuery::create('user.id', ['flx', 'fly']));
```

Pagination
----------

[](#pagination)

Finally the `Builder` also features a `size()` and `from()` method for the corresponding OpenSearch search parameters. These can be used to build a paginated search. Take a look the following example to get a rough idea:

```
use Sendy\OpenSearchQueryBuilder\Builder;

$pageSize = 100;
$pageNumber = $_GET['page'] ?? 1;

$transport = (new \OpenSearch\TransportFactory())->create();

$client = new \OpenSearch\Client($transport);

$pageResults = (new Builder($client))
    ->size($pageSize)
    ->from(($pageNumber - 1) * $pageSize)
    ->search();
```

Multi-Search Query Builder
--------------------------

[](#multi-search-query-builder)

OpenSearch provides a ["multi-search" API](https://docs.opensearch.org/docs/latest/api-reference/search-apis/multi-search/) that allows for multiple query bodies to be included in a single request.

Use the `MultiBuilder` class and [add builders](#add-builders) to add builders to your query request. The response will include a `responses` array of the query results, in the same order the requests are added. Use the `$multiBuilder->search()` to execute the queries, or `$multiBuilder->getPayload()` for the raw request payload.

```
use Sendy\OpenSearchQueryBuilder\MultiBuilder;
use Sendy\OpenSearchQueryBuilder\Builder;

$transport = (new \OpenSearch\TransportFactory())->create();

$client = new \OpenSearch\Client($transport);

$multiBuilder = (new MultiBuilder($client));

$multiBuilder->addBuilder(
    (new Builder($client))->index('custom_index')->size(10)
);
// you can pass the index name to the addBuilder method second param
$multiBuilder->addBuilder(
    (new Builder($client))->size(10)
    'different_index'
);

$multiResults = $multiBuilder->search();
```

Returns the following response JSON shape:

```
{
    "took": 2,
    "responses": [
        {... first query result ...},
        {... second query result ...},
    ]
}

```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

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

[](#contributing)

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

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

[](#security-vulnerabilities)

If you've found a bug regarding security please mail  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Alex Vanderbist](https://github.com/alexvanderbist)
- [Ruben Van Assche](https://github.com/rubenvanassche)
- [Sendy](https://github.com/sendynl)
- [All Contributors](../../contributors)

License
-------

[](#license)

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

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance51

Moderate activity, may be stable

Popularity23

Limited adoption so far

Community18

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor3

3 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.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

347d ago

### Community

Maintainers

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

---

Top Contributors

[![AlexVanderbist](https://avatars.githubusercontent.com/u/6287961?v=4)](https://github.com/AlexVanderbist "AlexVanderbist (47 commits)")[![freekmurze](https://avatars.githubusercontent.com/u/483853?v=4)](https://github.com/freekmurze "freekmurze (35 commits)")[![dam-bal](https://avatars.githubusercontent.com/u/94970129?v=4)](https://github.com/dam-bal "dam-bal (22 commits)")[![bram-pkg](https://avatars.githubusercontent.com/u/13304739?v=4)](https://github.com/bram-pkg "bram-pkg (13 commits)")[![floristenhove](https://avatars.githubusercontent.com/u/2111622?v=4)](https://github.com/floristenhove "floristenhove (12 commits)")[![wgriffioen](https://avatars.githubusercontent.com/u/53269?v=4)](https://github.com/wgriffioen "wgriffioen (11 commits)")[![chrispappas](https://avatars.githubusercontent.com/u/4694803?v=4)](https://github.com/chrispappas "chrispappas (7 commits)")[![rubenvanassche](https://avatars.githubusercontent.com/u/619804?v=4)](https://github.com/rubenvanassche "rubenvanassche (7 commits)")[![MilanLamote](https://avatars.githubusercontent.com/u/80511992?v=4)](https://github.com/MilanLamote "MilanLamote (5 commits)")[![AdrianMrn](https://avatars.githubusercontent.com/u/12762044?v=4)](https://github.com/AdrianMrn "AdrianMrn (5 commits)")[![l3aro](https://avatars.githubusercontent.com/u/25253808?v=4)](https://github.com/l3aro "l3aro (4 commits)")[![Nielsvanpach](https://avatars.githubusercontent.com/u/10651054?v=4)](https://github.com/Nielsvanpach "Nielsvanpach (4 commits)")[![chirag-techrayslabs](https://avatars.githubusercontent.com/u/160377961?v=4)](https://github.com/chirag-techrayslabs "chirag-techrayslabs (3 commits)")[![nick-rashkevich](https://avatars.githubusercontent.com/u/86295744?v=4)](https://github.com/nick-rashkevich "nick-rashkevich (3 commits)")[![summerKK](https://avatars.githubusercontent.com/u/19187969?v=4)](https://github.com/summerKK "summerKK (2 commits)")[![adevneverdies](https://avatars.githubusercontent.com/u/1610989?v=4)](https://github.com/adevneverdies "adevneverdies (1 commits)")[![djubelius](https://avatars.githubusercontent.com/u/119296273?v=4)](https://github.com/djubelius "djubelius (1 commits)")[![imdhemy](https://avatars.githubusercontent.com/u/22864831?v=4)](https://github.com/imdhemy "imdhemy (1 commits)")

---

Tags

opensearchphpspatiesearchopensearchSendyopensearch-query-builder

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sendynl-opensearch-query-builder/health.svg)

```
[![Health](https://phpackages.com/badges/sendynl-opensearch-query-builder/health.svg)](https://phpackages.com/packages/sendynl-opensearch-query-builder)
```

###  Alternatives

[opensearch-project/opensearch-php

PHP Client for OpenSearch

15224.3M65](/packages/opensearch-project-opensearch-php)[spatie/elasticsearch-query-builder

Build and execute an Elasticsearch search query using a fluent PHP API

183614.7k5](/packages/spatie-elasticsearch-query-builder)[spatie/laravel-site-search

A site search engine

300129.1k](/packages/spatie-laravel-site-search)[spatie/x-ray

Quickly scan source code for calls to Ray

80381.2k29](/packages/spatie-x-ray)[zing/laravel-scout-opensearch

Laravel Scout custom engine for OpenSearch

33340.2k](/packages/zing-laravel-scout-opensearch)[cmsig/seal

Search Engine Abstraction Layer

32207.9k53](/packages/cmsig-seal)

PHPackages © 2026

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