Vendure Advanced Search Plugin
Vendure Advanced Search Plugin
Vendure Advanced Search Plugin
Vendure Advanced Search Plugin
Vendure Advanced Search Plugin
Vendure Advanced Search Plugin
Vendure Advanced Search Plugin
Vendure Advanced Search Plugin
Vendure Advanced Search Plugin
Vendure Advanced Search Plugin

Vendure Advanced Search Plugin

Advanced, efficient, lightning-fast search with support for fuzzy matching, synonyms, curation, analytics and more

Annual License
  • Updates and support
  • Get updates 30 days before release
  • Access to source code
€1,152.00 / year
Billed annually upfront
Monthly License
  • Updates and support
  • Get updates 30 days before release
  • Access to source code
€120.00 / month

Join the Waitlist

Be the first to know when the new marketplace is available. We'll send you an email as soon as it launches.

Latest version2.2.1
Compatibility^2.0.0||^3.0.0
Last publishedFeb 18, 2025
Vendure GmbH

This plugin enables lightning-fast, memory-efficient search backed by Typesense, as well as powerful search analytics.

This is the most advanced and feature-rich search solution available for Vendure.

Features

  • Typo tolerance: Handle spelling mistakes with configurable typo tolerance.
  • Tunable weightings: Define what relative weight to give title, description and SKU.
  • Index any kind of custom data alongside your products, including custom field data
  • Filter & sort by custom fields
  • Advanced faceted searching
  • Geopoint search & filtering: search by distance from a given location.
  • Result pinning: Pin specific results to a particular position e.g. for merchandising.
  • Synonyms: Define synonyms to increase accuracy of search results
  • Custom search indexes: index any dataset into Typesense and expose via Vendure APIs.
  • Advanced analytics giving insights into popular searches, click-through rate, result rate etc.

Getting Started

Pre-requisites

Typesense

You'll need an instance of Typesense available, which is the underlying search engine used by this plugin.

You can quickly set up a local instance of Typesense using Docker:

or with Docker Compose:

Or you can set up an account with Typesense Cloud for a hosted solution.

Clickhouse (optional)

For the best support for search analytics, we strongly recommend Clickhouse:

docker pull yandex/clickhouse-server:24-alpine

Note: If you are self-hosting Clickhouse, the default configuration can use a lot of disk space due to the logging settings. Read Calming down Clickhouse for some tips on how to reduce disk usage.

If you do not want to use Clickhouse, we also support SQL-based analytics which will store the analytics data in your database.

It is also possible to use this plugin without analytics - see the Search Analytics guide.

Installation

Configuration

Add the plugin to your VendureConfig:

If not already installed, install the @vendure/ui-devkit package.

Then add the UI extensions. This plugin requires the ui-devkit in order to compile a custom Admin UI including the advanced search extensions. See our guide to extending the Admin UI.

Migration

Generate a database migration for the new custom field that will be set on the GlobalSettings entity.

Document Overrides

It is possible to override the indexed value of the built-in fields on the SearchResult type. For example, let's say you have a customField which stores a "sale price" which you want to use in your search results. Here's how you can configure it:

The override function will receive an object as it's single argument which looks like this:

Searching

The Advanced Search Plugin is a drop-in replacement for the DefaultSearchPlugin that comes with a standard Vendure installation. Therefore, the same search query is used to perform searches, and the same response shape is returned.

However, the Advanced Search Plugin provides a number of additional features which can be used to enhance the search experience.

Input fields

The regular SearchInput type is extended with the following additional fields:

The same search query is used to search in the Shop API, but the input object is extended as follows:

Response fields

The search response is also extended to include data on facet counts, price ranges, top collections, and more.

For reference, here are the definitions of the new types:

The heart of the search experience is the fulltext search. Typesense provides powerful, typo-tolerant and extremely fast fulltext search capabilities, which you use by providing a term input to the search query.

Prefix search

The prefixMode input can be used to perform a prefix search, which is useful for live search (autocomplete) use cases. When set to true, the last word of the query will be treated as a prefix, rather than a whole word:

Usually this would return no results, as there is no product with the name "baske". However, with prefixMode set to true, the search will return products with names such as "basketball", "basket", etc.

Highlights

The highlights field in the SearchResult type provides information about which fields matched the search query. This can be useful for displaying search results in a user-friendly way, e.g. by highlighting the matched terms in the product name.

The result will look like this:

You can use the snippet field directly in your client application to display the highlighted text by defining a CSS style for the <mark> tag.

Sorting

As with the DefaultSearchPlugin, the search query results can be sorted by price or name:

It is also possible to define extra fields by which to sort, by using the sortableFields option.

With the above configuration, we will then be able to perform queries such as:

Filtering

Filtering of the search results can be done via the built-in filter inputs of the SearchInput object such as collectionSlug, facetValueFilters, inStock, priceRangeWithTax.

Search results can be filtered in various ways using dedicated input fields:

Advanced filtering

For even more control over filtering you can use the filterBy input to provide expressions directly as Typesense filter_by parameters.

Custom mappings can be filtered by prepending the name with customMapping_:

Faceted Search

Faceted search is a powerful feature that allows users to refine search results by applying multiple filters. This can be implemented using the facetsFilter argument of the SearchInput object. This argument accepts the code of the facet and the codes of the selected facet values, and returns the updated facets with the remaining counts of their facet values in the facets property of the SearchResponse object.

Note: facets vs facetValues vs facetCounts

The SearchResponse type contains 3 fields related to facets, which can be a source of confusion. Here's what they mean:

  • facetValues: This contains a flat list of all the facet values that are present in the search results. This exists for backwards compatibility with the default search plugin, but is not recommended.
  • facetCounts: This contains the raw counts of faceted values from Typesense. This is mainly useful for debugging, as the returned values contain limited information, e.g. just the IDs for facet values.
  • facets: This is the recommended way to get faceted values. It groups the facet values by facet, and gives you more control over the results based on the facetsFilter input.

Likewise, the SearchInput object has 2 fields related to facets:

  • facetValueFilters: The backward-compatible way of filtering by facet value ID and is not recommended.
  • facetsFilter: This is the recommended way to implemented faceted search together with the facets field in the SearchResponse.

By setting includeNullValues: true in the facetsFilter argument, the response will also include facet values with a count of zero.

Here's an example of a GraphQL query implementing a faceted search:

This query will return a list of products filtered by the selected facets, along with the total number of items and the updated facets with their counts.

The results returned in the facets object will include the updated counts of the facet values for the selected facets. These can be used in the frontend to display the available facet values and their counts to the user.

This would then be used as the basis for a faceted search UI, where the user can select multiple facets to refine the search results.

Top Collections

The topCollections field in the SearchResponse object returns the top collections based on the search input. If differs from the collections field in the following way:

  • collections is a backward-compatible field that returns all collections in the result set. So if you are returning the first 10 results, you will get the collections of those 10 results. This means that the collections returned are affected by the pagination (skip, take) of the search query.
  • topCollections is more powerful because it allows you to return the collections related to the top n results, regardless of the pagination, by using the topCollectionsFromTop input.

Custom Mappings

Custom mappings allow you to index arbitrary data alongside your product variants. For example, if you have defined custom fields on your products or variants, you can index these fields and make them searchable.

Here are some example use cases for custom mappings:

For more information on each specific option, see:

Primitive & Object types

Primitive custom mappings return a primitive value (a GraphQL scalar or list of scalars such as Int or [String!]). The examples above are of this type, and these are suitable for most cases.

Sometimes, however, you may want to return a more complex type from a custom mapping. In this case, you can use an object custom mapping, which can return any kind of GraphQL object type.

An object custom mapping must:

  1. Specify a typesenseType which is one of the Typesense field types. This is required because we cannot infer the Typesense type from the GraphQL type, since the GraphQL type can be anything.
  2. Specify an outputFn which has the task of transforming the data stored in Typesense into the correct shape according to the chosen graphQlType. The values it receives will be an array, and in the case that the search has been performed with groupByProduct: true, it will contain all the values for each variant in the given product. The groupByProduct argument will be true if the search was performed with groupByProduct: true, otherwise it will be false.
  3. If a GraphQL type has been used which does not yet exist in your schema, you can define it using the graphQlSchemaExtension property.

In this example, we have defined a custom rrp (recommended retail price) field on the Product, and we want to expose it as a min/max range:

Querying custom mappings

Custom mappings are exposed via the customMappings file in the search response type.

Sorting by custom mappings

Search results can be sorted by custom mappings by configuring the sortableFields config option.

Then, in your GraphQL query, you can sort by the custom mapping:

Filtering by custom mappings

Results can be filtered by custom mappings by using the filterBy input field and specifying the custom mapping name in the format customMapping_<name>. The value should be a string in the format of the Typesense filter syntax.

Geosearch

Typesense supports powerful geosearch capabilities, which you use to sort results based on distance from a given point. Here's an example of how this would work.

First, we need to store coordinates somewhere. In this example, we will store them in custom fields on the Product entity:

Next, we need to define a custom mapping for the latitude and longitude fields:

(here we are assuming that the custom fields have been correctly typed)

We also need to specify that the location custom mapping is sortable:

Finally, we can use the sort input to sort by distance from a given point, filter by distance from that point, and use the geoDistance field to get the distance in meters.

  • The sort input is used to sort results by distance from a given latitude and longitude.
  • The filterBy input is used to only include results within a certain distance from a given latitude and longitude.

External Indexes

An external index allows you to index and search any dataset alongside your product data. This data can come from any source, such as within Vendure itself or from an external system.

Example: Indexing collections

Here's an example where we index Vendure collections, so we can display them in the search results:

Example: Indexing blog posts

As another example, you might want to index blog posts or information pages. This is done by defining an ExternalIndex instance. Here's an example which indexes articles from a CMS:

You then pass this ExternalIndex instance to the AdvancedSearchPlugin:

Searching external indexes

For each external index defined, the plugin will automatically generate a new query in the GraphQL API. The name of the query will be search<Name>, where Name is the name of the index converted to PascalCase. This can be overridden by specifying the queryName property when defining the ExternalIndex.

Searching multiple external indexes

It is also possible to search multiple external indexes in a single query using the searchExternal query. The response will be a union type of all the external indexes you have defined. The plugin will automatically create a GraphQL type for your external index, which will be named as a PascalCase version of the name property with a Response suffix. In the example above, the name is 'cms-articles', so the resulting GraphQL type would be CmsArticlesResponse.

The ExternalSearchInput object must then specify the name of the external index(es) to search:

In this example we are also providing some custom filterBy data, which allows your external index to support arbitrary filtering expressions supported by Typesense.

Search Analytics

Analytics on searches can be collected based on the configured analytics strategy. The plugin comes with two strategies out of the box:

  • SqlAnalyticsStrategy: This strategy stores analytics data in a SQL database. This is the default strategy.
  • ClickhouseAnalyticsStrategy: This strategy stores analytics data in a Clickhouse database.

When performing a search, setting the logAnalytics input field to true will record that search in the analytics store.

Note that the analytics engine will automatically "debounce" intermediate searches during an autocomplete, so that only the final search is recorded. For example, if the user types "red", then "red sh" and then "red shoes", only the "red shoes" search will be recorded. The window of time during which intermediate searches are ignored can be configured via the analytics.aggregationWindowMs option.

In your storefront, you should then implement the following mutations in order to track search result views and click-throughs:

The logSearchListViewed mutation allows us to register the fact that the search results were viewed. This can be used for both a full results page, or an autocomplete list.

The logSearchResultClicked mutation allows us to register the fact that a specific search result was clicked. This in turn allows the analytics engine to calculate click rate and average click position for each search term.

Disabling Analytics

If you do not need the analytics features, they can be disabled with the following configuration (since v1.4.0):