Search Module
In this guide, you'll learn about the Search Module, its providers, and its index definitions.
What is the Search Module?#
The Search Module provides full-text search functionality in your Medusa application. It indexes your data in a search engine, then serves queries with relevance ranking, filters, facets, highlighting, and vector search.
For example, you can use the Search Module to power a storefront's product search with typo tolerance and category facets, or to let admin users find orders by customer name.
The Search Module writes to and reads from the search engine you integrate, such as PostgreSQL or Medusa Search for Cloud users. This gives you flexibility in choosing the search infrastructure that matches your performance and scalability requirements.
How the Search Module Works#
The Search Module has two parts that you control:
- Search Module Providers: they implement the logic of talking to a search engine.
- Search index definitions: you declare which entity to index, which fields the engine holds, and how to fill the index.
The Search Module handles everything between the two. It creates and migrates the physical indexes, fills them when they are empty, applies events to keep them current, and compiles a search query into whatever the provider's engine understands.
It also keeps a version per physical index it builds, so a definition change builds a new version alongside the one serving reads and only makes it active once it's filled. Every provider rebuilds an index that way, so a schema change costs no downtime whichever one you use.
Configure the Search Module#
As of v2.21.1, the Search Module is registered by default in your Medusa application with the PostgreSQL Search Module Provider, so you only declare the indexes to search. The module itself needs no extra configuration.
On Cloud, Medusa Search is registered by default for all plans, unless you explicitly configure a different provider.
Change Default Search Configuration#
To change the Search Module's configuration or use a different provider, add the Search Module to the modules property of the exported object in medusa-config.ts:
1// To register only for development. This is necessary to use Medusa Search in Cloud2const isDev = process.env.NODE_ENV !== "production"3 4module.exports = defineConfig({5 // ...6 modules: [7 isDev && {8 resolve: "@medusajs/medusa/search",9 options: {10 index_prefix: "prod_",11 providers: [12 {13 resolve: "@medusajs/medusa/search-postgres",14 id: "search-postgres",15 },16 ],17 },18 },19 ].filter(Boolean),20})
Run Migrations#
If you upgraded an existing Medusa application to v2.21.1, run the following command to create the necessary database tables for the Search Module and its configured provider:
Search Module Options#
Option | Description | Default |
|---|---|---|
| An array of Search Module Providers to register. You can register more than one provider and point each index at a different engine. | The PostgreSQL provider, whose identifier is |
| The identifier of the provider to use for index definitions that don't name one. Required when you register more than one provider. Its value is the | If only one provider is registered, the module uses it as the default. Otherwise, an error is thrown while the module initializes. |
| A string prepended to every physical index name. Use it when multiple applications share a search engine. | No prefix. |
| The number of documents the module writes to the engine per request while filling an index. |
|
Search Module Providers#
A Search Module Provider implements the logic of indexing and searching documents in a search engine. The Search Module uses the provider that each index definition resolves to.
Medusa ships the PostgreSQL Search Module Provider, and Medusa Search that's available for Cloud users only. You can also create your own.
Refer to the Search Module Providers guide to learn how to register providers, choose a default, and point an index at a specific provider.
Search Products#
Medusa doesn't declare any search index by default. If you installed your Medusa application after v2.21.1, it already has a product index definition. Otherwise, you need to define a product index in your Medusa application to make your products searchable. Then, you need to make it searchable on the Store Search API route.
1. Define the Product Index#
An index definition names the entity to index, the fields the engine holds, a seed that fills the index, and the events that keep it up-to-date.
Define the product index in the file src/search/product.ts:
1import {2 defineSearchIndex,3 graphConsume,4 graphSeed,5 search,6} from "@medusajs/framework/utils"7 8const source = {9 fields: [10 "id",11 "title",12 "description",13 "handle",14 "thumbnail",15 "status",16 "categories.id",17 "categories.name",18 "categories.handle",19 "tags.id",20 "tags.value",21 ],22 transform: (products) => {23 return products24 .filter((p) => p.status === "published")25 .map(({ status, ...product }) => product)26 },27}28 29export const productIndex = defineSearchIndex({30 name: "product",31 entity: "product",32 fields: search.define({33 id: search.keyword().filterable(),34 title: search.text().searchable({ weight: 3 }),35 description: search.text().searchable(),36 handle: search.keyword().filterable(),37 thumbnail: search.keyword(),38 categories: search39 .object({40 id: search.keyword().filterable(),41 name: search42 .keyword()43 .searchable()44 .facetable(),45 handle: search.keyword().filterable(),46 })47 .array(),48 tags: search49 .object({50 id: search.keyword().filterable(),51 value: search52 .keyword()53 .searchable()54 .facetable(),55 })56 .array(),57 }),58 events: [59 "product.created",60 "product.updated",61 "product.deleted",62 ],63 consume: graphConsume(source),64 seed: graphSeed(source),65})
seed fills the index in full when Medusa creates or rebuilds it, and Medusa routes each of the workflow events in events to consume, so the index reflects changes as they happen.
graphSeed and graphConsume are helpers that read the data with Query. graphSeed reads the entity's records to fill the index for the first time, and graphConsume provides the index changes, such as creating or deleting records, as the events in events arrive.
Add to the definition any core or custom field that your storefront searches, filters, sorts, or facets on. Learn more about the available field types and modifiers in the Search Index Definitions guide.
2. Run Migrations#
Next, run the migrations command to create the physical index:
Medusa then fills the index with your products. Learn more about index migrations in the Reindexing and Migrations guide.
3. Allow the Index on the Store Search Route#
Medusa exposes the Store Search API route, which searches any index of your application. However, an index isn't searchable through it until a middleware allows it, so a storefront never reaches an index you didn't expose.
To allow the product index, use the configureStoreSearch middleware in src/api/middlewares.ts:
You can allow more than one index, and you can add the middleware more than once, so a plugin exposes its own index without discarding what your application allowed.
The route also narrows a product index to the products a storefront may see, and you can narrow any index further with a filter of your own. Learn more in the Store Search Route guide.
4. Search Products#
Finally, send a POST request to /store/search with the index to search in the entity field:
You'll receive a JSON response like the following:
1{2 "results": [3 {4 "hits": [5 {6 "id": "prod_01KXR3J9J610DT161E2E4ZS6P1",7 "score": 1.23,8 "document": {9 "id": "prod_01KXR3J9J610DT161E2E4ZS6P1",10 "title": "Medusa T-Shirt",11 "handle": "t-shirt"12 }13 }14 ],15 "metadata": {16 "skip": 0,17 "take": 20,18 "count": 1,19 "query": "shirt",20 "processing_time_ms": 921 }22 }23 ]24}
The route answers with one result per posted query, each holding the matching hits ranked by relevance, any facets you asked for, and the query's metadata.
You can also post a batch of queries at once in a queries array, and each of them runs against the index it names in one round-trip to the search engine.
Search Other Entities#
To search data other than products, such as the custom data models of your own modules, declare an index definition for it in a file under the src/search directory, then run the migrations command.
For example, to index a custom brand data model, create the file src/search/brand.ts:
1import {2 defineSearchIndex,3 graphConsume,4 graphSeed,5 search,6} from "@medusajs/framework/utils"7 8const fields = ["id", "name", "country", "created_at"]9 10export const brandIndex = defineSearchIndex({11 name: "brand",12 entity: "brand",13 fields: search.define({14 id: search.keyword().filterable(),15 name: search.text().searchable({ weight: 3 }),16 country: search.keyword().filterable().facetable(),17 created_at: search.date().sortable(),18 }),19 events: [20 "brand.created",21 "brand.updated",22 "brand.deleted",23 ],24 consume: graphConsume({ fields }),25 seed: graphSeed({ fields }),26})
Then, choose how to search it:
- To search it from your storefront, add
brandto theconfigureStoreSearchmiddleware, as explained in Allow the Index on the Store Search Route, then post a query with"entity": "brand". - To search it in your own API route or workflow, use the
searchmethod of Query.
For example, to search brands in a custom API route, create the file src/api/store/brands/search/route.ts with the following content:
1import {2 MedusaRequest,3 MedusaResponse,4 ContainerRegistrationKeys,5} from "@medusajs/framework/http"6 7export const GET = async (8 req: MedusaRequest,9 res: MedusaResponse10) => {11 const query = req.scope.resolve(12 ContainerRegistrationKeys.QUERY13 )14 15 const { data, search_result } = await query.search({16 entity: "brand",17 fields: ["id", "name", "country"],18 filters: {19 q: req.query.q as string,20 },21 pagination: { take: 20 },22 })23 24 res.json({25 brands: data,26 metadata: search_result.metadata,27 })28}
Then, send a GET request to the route:
You'll receive a JSON response like the following:
Learn about filters, facets, highlighting, and vector search in the Search Queries guide.