Blog

September 23, 2025·Product

Improved Medusa Index

Oliver Juhl

Oliver avatar

Adrien de Peretti

Adrien avatar

Oliver Juhl & Adrien de Peretti

Unlock better cross-module filtering and faster queries with the new Medusa Index improvements.

Image modal

We are excited to release improvements to Medusa Index, making all core entities indexable.

Medusa Index was built to improve the developer experience of filtering data across modules while improving the performance of data retrieval, especially for large data sets. In previous versions, only a select few entities were indexable, including Products, Variants, Prices, and Sales Channels.

In version 2.10.2, we expanded the indexable entities to include all entities in our core modules and entities from custom modules in your Medusa project. This improvement unlocks cross-module filtering for all data in your applications, provided the data is ingested into Medusa Index.

You can read more about the technical design of Medusa Index in our documentation.

Installing Medusa Index

Medusa Index is still in development, so it is not used by default in the core APIs and workflows. To use Index, you need to follow a few steps to install it in your Medusa project.

Install the Copy to clipboardnpm package:

yarn add @medusajs/index

Add Index as a module in Copy to clipboardmedusa-config.ts:

module.exports = defineConfig({
// ...
modules: [
// ...
{
resolve: "@medusajs/index",
},
],
})

Run the migrations to create the database tables related to Index:

npx medusa db:migrate

Start the server:

yarn dev

Indexing occurs when the server is started. As mentioned, we ingest Products, Variants, Prices, and Sales Channels by default, so the first indexation might take a while if your product catalog is large. You should see the following logs when the indexation has finished:

info: [Index engine] Checking for index changes
info: [Index engine] Found 7 index changes that are either pending or processing
info: [Index engine] syncing entity 'ProductVariant'
info: [Index engine] syncing entity 'ProductVariant' done (+38.73ms)
info: [Index engine] syncing entity 'Product'
info: [Index engine] syncing entity 'Product' done (+18.21ms)
info: [Index engine] syncing entity 'LinkProductVariantPriceSet'
info: [Index engine] syncing entity 'LinkProductVariantPriceSet' done (+33.87ms)
info: [Index engine] syncing entity 'Price'
info: [Index engine] syncing entity 'Price' done (+22.79ms)
info: [Index engine] syncing entity 'PriceSet'
info: [Index engine] syncing entity 'PriceSet' done (+10.72ms)
info: [Index engine] syncing entity 'LinkProductSalesChannel'
info: [Index engine] syncing entity 'LinkProductSalesChannel' done (+11.45ms)
info: [Index engine] syncing entity 'SalesChannel'
info: [Index engine] syncing entity 'SalesChannel' done (+7.00ms)

After this, the installation of Index is completed.

You can now try Index by creating a custom API endpoint and querying your Products:

import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
export async function GET(req: MedusaRequest, res: MedusaResponse) {
const query = req.scope.resolve("query")
const { data: products } = await query.index({
entity: "product",
fields: ["id", "sales_channels.*"],
filters: {
sales_channel: {
id: "sc_12345",
},
},
})
res.json({ products })
}

Note the use of Copy to clipboardquery.index instead of Copy to clipboardquery.graph.

Alternatively, you can enable the following feature flag and try it out in two core routes, Copy to clipboardGET /store/products and Copy to clipboardGET /admin/products:

module.exports = defineConfig({
// ...
featureFlags: {
index_engine: true
},
modules: [
// ...
{
resolve: "@medusajs/index",
},
],
})

In a later version, we will enable Index by default across a range of APIs.

Enabling cross-module filtering of core entities

It is a relatively common use case to want to filter core entities by an entity from a custom module. Let's demonstrate this with an example of filtering Orders from Medusa's core by a custom entity, Vendor.

This example assumes three things:

  • A custom module with a Vendor data model exists in your Medusa project
  • A link definition between Orders and Vendors exists
  • Orders are linked to Vendors when they are created

Read more about managing links in our documentation.

To ingest data into Index, you need to use a new configuration Copy to clipboardfilterable in the link definition, so the first step is to update the link definition we already had:

import { defineLink } from "@medusajs/framework/utils";
import OrderModule from "@medusajs/medusa/order";
import VendorModule from "../modules/vendor";
export default defineLink(
{
linkable: OrderModule.linkable.order,
isList: true,
filterable: ["id", "display_id", "shipping_address"]
},
{
linkable: VendorModule.linkable.vendor,
filterable: ["id", "name"],
}
);

The Copy to clipboardfilterable option changes a regular link to an indexed link. In addition to this, Copy to clipboardfilterable defines what fields on the entities you want indexed; in other words, what fields you want to be able to filter by. With this link definition in place, Orders and Vendors are ingested into Index whenever either side is created, updated, or deleted.

Finally, you can now put Index to use and easily filter Orders by Vendors using the Copy to clipboardquery.index API:

const { data: orders } = await query.index({
entity: "order",
fields: ["id", "vendor.*"],
filters: {
vendor: {
id: "vend_01K5DSYHNTWF9N4B3Q3S4STFGN",
},
},
});

We are continuously improving Index and will soon add both an API and an admin UI to manage data, including the ability to trigger a sync.

To try Index today, follow the steps in this post or check out the installation guide in our documentation.

Share this post

Ready to build your custom commerce setup?