Store Search API Route

In this guide, you'll learn how to expose your search indexes to a storefront through the Store Search API route, and how to control what a storefront can find in them.

What is the Store Search API Route?#

Medusa exposes a single POST /store/search route that searches any index of your application, so you don't create an API route per index. It answers with the search engine's own results, which is the contract the InstantSearch adapter is built on.

The route exposes nothing by default. An index is only searchable through it once the configureStoreSearch middleware allows it, and a request for any other index is answered as if the index didn't exist. That way, the route never tells a storefront what your application holds.


Allow Indexes on the Route#

To allow an index to be searched through the route, apply the configureStoreSearch middleware to /store/search in src/api/middlewares.ts:

src/api/middlewares.ts
1import {2  configureStoreSearch,3  defineMiddlewares,4} from "@medusajs/framework/http"5
6export default defineMiddlewares({7  routes: [8    {9      matcher: "/store/search",10      middlewares: [11        configureStoreSearch({12          allowed_indexes: {13            product: true,14            product_category: true,15          },16        }),17      ],18    },19  ],20})

A storefront can now post a query with "entity": "product" or "entity": "product_category". Every other index still answers with a 404 error, and Medusa logs why for whoever configured the route.

You can apply the middleware more than once. Each one merges its allowed_indexes into the ones before it, so a plugin exposes its own index without discarding what your application allowed.

Tip: Only allow indexes whose documents are public. An index holding internal data, such as one you built for Medusa Admin's search, stays unreachable from a storefront as long as you don't allow it.

How Medusa Narrows a Product Index#

A storefront must never find a draft product, or a product that isn't in the request's sales channel. So, for an index whose entity is product, Medusa adds the following filters to every query:

Filter

Applied when

status is published.

The index declares a status field with the filterable modifier.

sales_channel_ids is one of the publishable API key's sales channels.

The index declares a sales_channel_ids field with the filterable modifier.

So, to have Medusa scope your product index for you, declare both fields in the index definition. sales_channel_ids isn't a field of the product itself, so select the product's sales_channels.id and flatten them onto the document with a transform:

src/search/product.ts
1const productFields = search.define({2  id: search.keyword().filterable().retrievable(),3  title: search4    .text()5    .searchable({ weight: 3 })6    .retrievable(),7  // Read by `/store/search` to scope the index.8  status: search.keyword().filterable().retrievable(false),9  sales_channel_ids: search10    .keyword()11    .array()12    .filterable()13    .retrievable(false),14  // ...15})16
17const source = {18  fields: ["id", "title", "status", "sales_channels.id"],19  transform: (products) => {20    return products21      .filter((p) => p.status === "published")22      .map((product) => ({23        id: product.id,24        title: product.title,25        status: product.status,26        sales_channel_ids: product.sales_channels.map(27          (salesChannel) => salesChannel.id28        ),29      }))30  },31}32
33export default defineSearchIndex({34  name: "product",35  entity: "product",36  fields: productFields,37  consume: graphConsume(source),38  seed: graphSeed(source),39  // ...40})

The status and sales_channel_ids fields use retrievable(false), so the route can filter on them without ever returning them in a hit.

Tip: The index in this snippet is simplified to show one feature at a time. For examples of indexing prices in multiple currencies, option values, categories, and other product data, refer to the Product Index Examples guide.

If the index is missing either field, Medusa logs a warning once for that index and skips that filter. Then, either add the missing field to the definition, or scope the index yourself as explained in the next section.


Filter an Index for Your Storefront#

To constrain an index beyond what Medusa applies, pass a filters function for it instead of true. The function receives the request, so a constraint can depend on who is asking, and it can be asynchronous.

For example, to only find active product categories:

src/api/middlewares.ts
1import {2  configureStoreSearch,3  defineMiddlewares,4} from "@medusajs/framework/http"5
6export default defineMiddlewares({7  routes: [8    {9      matcher: "/store/search",10      middlewares: [11        configureStoreSearch({12          allowed_indexes: {13            product: true,14            product_category: {15              filters: (req) => ({ is_active: true }),16            },17          },18        }),19      ],20    },21    // ...22  ],23})

Medusa combines your filters with the ones it applies itself and the ones the storefront posts using an $and operator. So, a storefront can narrow its own results but never widen them past what you allowed.


Search Documents in the Storefront#

Once an index is allowed, send a POST request to /store/search with the index's name in entity:

Code
1curl -X POST "http://localhost:9000/store/search" \2  -H "x-publishable-api-key: pk_123" \3  -H "Content-Type: application/json" \4  --data '{5    "entity": "product",6    "filters": { "q": "shirt" }7  }'

Learn more about the accepted request body and the response in the Store Search API reference.

Tip: To build a full search experience with facets, pagination, and sorting in your storefront, use the InstantSearch adapter, which posts to this route for you.
Was this page helpful?
Ask Bloom
For assistance in your development, use Claude Code Plugins or Medusa MCP server in Cursor, VSCode, etc...FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break