Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions dev-server/dev-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { DataSourceOptions } from 'typeorm';
// import { StellatePlugin } from '../packages/stellate-plugin/src/stellate-plugin';
// import { PubSubPlugin } from '../packages/pub-sub-plugin/src/plugin';
// import { PunchOutGatewayPlugin } from '../packages/punchout-gateway-plugin/src/punchout-gateway-plugin';
// import { MeilisearchPlugin } from '../packages/meilisearch-plugin/src/plugin';

/**
* Dev server config for testing community plugins during development.
Expand Down Expand Up @@ -72,6 +73,33 @@ export const devConfig: VendureConfig = {
assetUploadDir: path.join(__dirname, 'assets'),
}),
DefaultSearchPlugin.init({ bufferUpdates: false, indexStockStatus: false }),
// To use MeilisearchPlugin, comment out DefaultSearchPlugin above and uncomment below:
// MeilisearchPlugin.init({
// host: process.env.MEILISEARCH_HOST || 'http://localhost:7700',
// apiKey: process.env.MEILISEARCH_API_KEY || '',
// synonyms: {
// phone: ['mobile', 'smartphone', 'cellphone'],
// laptop: ['notebook', 'portable computer'],
// tv: ['television', 'monitor', 'screen'],
// shoe: ['sneaker', 'boot', 'footwear'],
// },
// stopWords: ['the', 'a', 'an', 'is', 'for', 'and', 'of', 'to', 'in'],
// typoTolerance: {
// enabled: true,
// minWordSizeForOneTypo: 4,
// minWordSizeForTwoTypos: 8,
// disableOnAttributes: ['sku'],
// },
// searchConfig: {
// matchingStrategy: 'frequency',
// attributesToHighlight: ['productName', 'description'],
// highlightPreTag: '<mark>',
// highlightPostTag: '</mark>',
// attributesToCrop: ['description'],
// cropLength: 30,
// showRankingScore: true,
// },
// }),
DefaultJobQueuePlugin.init({}),

// --- Community plugins ---
Expand Down
52 changes: 31 additions & 21 deletions docs/docs/reference/elasticsearch-plugin/elasticsearch-options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ Configuration options for the [ElasticsearchPlugin](/reference/elasticsearch-plu

```ts title="Signature"
interface ElasticsearchOptions {
host?: string;
port?: number;
adapter: () => SearchClientAdapter;
connectionAttempts?: number;
connectionAttemptInterval?: number;
clientOptions?: ClientOptions;
indexPrefix?: string;
indexSettings?: object;
indexMappingProperties?: {
Expand Down Expand Up @@ -41,34 +39,46 @@ interface ElasticsearchOptions {

<div className="members-wrapper">

### host
### adapter

<MemberInfo kind="property" type={`string`} default={`'http://localhost'`} />
<MemberInfo kind="property" type={`() => SearchClientAdapter`} />

The host of the Elasticsearch server. May also be specified in `clientOptions.node`.
### port
Factory that produces a `SearchClientAdapter` on demand.

<MemberInfo kind="property" type={`number`} default={`9200`} />
The plugin invokes this factory **once per NestJS provider** that
needs a search backend — currently the read-side `ElasticsearchService`
and the write-side `ElasticsearchIndexerController`. Each provider
therefore owns an independent client / connection pool, so tearing
one down during `onModuleDestroy` cannot starve in-flight requests
on the other. A single shared instance would be torn down twice and
take both providers offline; hence the factory shape.

The port of the Elasticsearch server. May also be specified in `clientOptions.node`.
The typical call site is a thin arrow wrapping one of the first-party
factories, e.g.:

*Example*

```ts
ElasticsearchPlugin.init({
adapter: () => createElasticsearchAdapter({ host, port }),
});
```

Backend-specific configuration (host, port, auth, AWS SigV4, etc.)
lives on the inner factory, so swapping backends only touches the
factory call and none of the shared plugin options below.
### connectionAttempts

<MemberInfo kind="property" type={`number`} default={`10`} />

Maximum amount of attempts made to connect to the ElasticSearch server on startup.
Maximum amount of attempts made to connect to the search server on
startup.
### connectionAttemptInterval

<MemberInfo kind="property" type={`number`} default={`5000`} />

Interval in milliseconds between attempts to connect to the ElasticSearch server on startup.
### clientOptions

<MemberInfo kind="property" type={`ClientOptions`} />

Options to pass directly to the
[Elasticsearch Node.js client](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html). For example, to
set authentication or other more advanced options.
Note that if the `node` or `nodes` option is specified, it will override the values provided in the `host` and `port` options.
Interval in milliseconds between attempts to connect to the search
server on startup.
### indexPrefix

<MemberInfo kind="property" type={`string`} default={`'vendure-'`} />
Expand Down Expand Up @@ -361,7 +371,7 @@ extend input SearchResultSortParameter {


</div>
<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="397" packageName="@vendure-community/elasticsearch-plugin" />
<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="404" packageName="@vendure-community/elasticsearch-plugin" />

Configuration options for the internal Elasticsearch query which is generated when performing a search.

Expand Down Expand Up @@ -639,7 +649,7 @@ searchConfig: {


</div>
<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="683" packageName="@vendure-community/elasticsearch-plugin" />
<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="690" packageName="@vendure-community/elasticsearch-plugin" />

Configuration for [boosting](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#field-boost)
the scores of given fields when performing a search against a term.
Expand Down
179 changes: 148 additions & 31 deletions docs/docs/reference/elasticsearch-plugin/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,178 @@
title: "ElasticsearchPlugin"
generated: true
---
<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/plugin.ts" sourceLine="68" packageName="@vendure-community/elasticsearch-plugin" />

This plugin allows your product search to be powered by [Elasticsearch](https://github.com/elastic/elasticsearch) — a powerful open source search
engine. This is a drop-in replacement for the DefaultSearchPlugin which exposes many powerful configuration options enabling your storefront
to support a wide range of use-cases such as indexing of custom properties, fine control over search index configuration, and to leverage
advanced Elasticsearch features like spacial search.
<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/plugin.ts" sourceLine="73" packageName="@vendure-community/elasticsearch-plugin" />

This plugin powers your product search via a pluggable search backend. It
ships with ready-made adapters for
[Elasticsearch](https://github.com/elastic/elasticsearch) and
[OpenSearch](https://github.com/opensearch-project/OpenSearch), and lets
you provide your own adapter by implementing
`SearchClientAdapter`. This is a drop-in replacement for the
DefaultSearchPlugin which exposes many powerful configuration options
enabling your storefront to support a wide range of use-cases such as
indexing of custom properties, fine control over search index
configuration, and advanced features like spatial search.

## Version Requirements

**ElasticSearch v9.1.0 is supported**

The version of ElasticSearch that is deployed, the version of the JS library @elastic/elasticsearch installed in your Vendure project and the version
of the JS library @elastic/elasticsearch used in the @vendure/elasticsearch-plugin must all match to avoid any issues. ElasticSearch does not allow @latest
in its repository so these versions must be updated regularly.

| Package | Version |
| ------------- | ------------- |
| ElasticSearch | v9.1.0 |
| @elastic/elasticsearch | v9.1.0 |
| @vendure/elasticsearch-plugin | v3.5.0 |
| Last updated | Dec 2, 2025 |
Vendure v3.6+ requires Elasticsearch v9.1 or newer. When using OpenSearch, the
3.x client / 3.x server line is supported.

The version of the search engine that is deployed, the version of the
JavaScript client installed in your Vendure project and the version of that
same client used internally by `@vendure-community/elasticsearch-plugin` must
all match to avoid any issues. Neither client allows `@latest` in its public
repository, so these versions must be updated regularly.

| Package | Minimum version |
| ---------------------------------------- | --------------- |
| `@vendure/core` | `3.6.0` |
| `@vendure-community/elasticsearch-plugin`| `2.0.0` |
| Elasticsearch (server + client) | `9.1.0` |
| OpenSearch (server + client) | `3.0.0` |

With Elasticsearch v8+, basic authentication, SSL, and TLS are enabled by
default and may result in your client and plugin not being able to connect to
Elasticsearch successfully if your client is not configured appropriately. You
must also set `xpack.license.self_generated.type=basic` if you are using the
free Community Edition of Elasticsearch.

Review the Elasticsearch docker
[example](https://github.com/vendure-ecommerce/vendure/blob/master/docker-compose.yml)
here for development and testing without authentication and security enabled.
Refer to the Elasticsearch documentation to enable authentication and security
in production.

With ElasticSearch v8+, basic authentication, SSL, and TLS are enabled by default and may result in your client and plugin not being able to connect to
ElasticSearch successfully if your client is not configured appropriately. You must also set `xpack.license.self_generated.type=basic` if you are
using the free Community Edition of ElasticSearch.
## Installation

Review the ElasticSearch docker [example](https://github.com/vendure-ecommerce/vendure/blob/master/docker-compose.yml) here for development
and testing without authentication and security enabled. Refer to ElasticSearch documentation to enable authentication and security in production.
Install the plugin plus exactly **one** of the two search clients:

## Installation
```shell
# Elasticsearch
npm install @vendure-community/elasticsearch-plugin @elastic/elasticsearch
```

```shell
npm install @elastic/elasticsearch @vendure/elasticsearch-plugin
# OpenSearch
npm install @vendure-community/elasticsearch-plugin @opensearch-project/opensearch
```

Make sure to remove the `DefaultSearchPlugin` if it is still in the VendureConfig plugins array.
Both clients are declared as `optional` peer dependencies — only install the
one you use. Make sure to remove the `DefaultSearchPlugin` from your
`VendureConfig` plugins array.

## Setup

Then add the `ElasticsearchPlugin`, calling the `.init()` method with `ElasticsearchOptions`:
Build the adapter for the backend you want to use and pass it to
`ElasticsearchPlugin.init()`.

### Elasticsearch

```ts
import { ElasticsearchPlugin } from '@vendure/elasticsearch-plugin';
import { ElasticsearchPlugin, createElasticsearchAdapter } from '@vendure-community/elasticsearch-plugin';

const config: VendureConfig = {
// Add an instance of the plugin to the plugins array
plugins: [
ElasticsearchPlugin.init({
host: 'http://localhost',
port: 9200,
// `adapter` is a factory: the plugin invokes it once per internal
// NestJS provider so each gets its own client / connection pool.
adapter: () =>
createElasticsearchAdapter({
host: 'http://localhost',
port: 9200,
// Any additional @elastic/elasticsearch ClientOptions
// (auth, tls, cloud, headers, etc.) may be provided via `clientOptions`.
// clientOptions: { auth: { username: 'elastic', password: 'changeme' } },
}),
indexPrefix: 'vendure-',
}),
],
};
```

### OpenSearch

```ts
import { ElasticsearchPlugin, createOpenSearchAdapter } from '@vendure-community/elasticsearch-plugin';

const config: VendureConfig = {
plugins: [
ElasticsearchPlugin.init({
adapter: () =>
createOpenSearchAdapter({
host: 'http://localhost',
port: 9200,
// Any additional @opensearch-project/opensearch ClientOptions
// (auth, ssl, awssigv4, headers, etc.) may be provided via `clientOptions`.
}),
indexPrefix: 'vendure-',
}),
],
};
```

### Custom adapter

`SearchClientAdapter` is a public TypeScript interface. You can implement your
own adapter (e.g. to use a managed/hosted service with a custom SDK, or to
inject a test double) and pass it directly:

```ts
import { ElasticsearchPlugin, SearchClientAdapter } from '@vendure-community/elasticsearch-plugin';

class MyCustomAdapter implements SearchClientAdapter { /* ... */ }

ElasticsearchPlugin.init({
// Return a fresh instance per call. The plugin invokes the factory once
// per internal provider (the read-side service and the write-side indexer),
// so each gets its own client and connection pool — bulk indexing cannot
// saturate the pool serving live search queries, and shutdown closes them
// independently.
adapter: () => new MyCustomAdapter(),
});
```

If you need direct access to the underlying client (for example to issue a
query that is not on the `SearchClientAdapter` surface), each built-in adapter
exposes its native client via `adapter.getRawClient()`.

## Migrating from v1.x

Versions prior to `2.0.0` shipped as `@vendure/elasticsearch-plugin` and
accepted `host` / `port` directly in `ElasticsearchPlugin.init(...)`. The
v2 release introduces the adapter pattern so the same plugin can power both
Elasticsearch and OpenSearch.

**Before (v1.x):**

```ts
ElasticsearchPlugin.init({
host: 'http://localhost',
port: 9200,
});
```

**After (v2.x):**

```ts
ElasticsearchPlugin.init({
adapter: () =>
createElasticsearchAdapter({
host: 'http://localhost',
port: 9200,
}),
});
```

Note the arrow: `adapter` accepts a **factory** that produces a
`SearchClientAdapter`, not an adapter instance directly. The plugin calls
the factory once per internal provider so each owns its own client.

The `clientOptions` property that previously lived at the top level of
`ElasticsearchOptions` now lives on the adapter factory options and is passed
through to the underlying client constructor verbatim.

## Search API Extensions

This plugin extends the default search query of the Shop API, allowing richer querying of your product data.
Expand Down
Loading
Loading