Medusa Search API Settings

In this guide, you'll learn about the settings and options that Medusa Search supports on indexes, fields, and queries.

Aside from the fields and settings that every index definition declares, Medusa Search supports settings and options of its own.

Tip: The product indexes in this guide's snippets are 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.

Index Settings#

Set the settings property of an index definition to configure how Medusa Search treats the whole index.

For example:

src/search/product.ts
1export const productIndex = defineSearchIndex({2  name: "product",3  entity: "product",4  fields: search.define({5    id: search.keyword().filterable(),6    title: search.text().searchable(),7    brand: search.keyword().filterable(),8  }),9  settings: {10    distinct_attribute: "brand",11    provider_options: {12      "search-medusa": {13        distance_metric: "cosine_distance",14      },15    },16  },17  async *seed({ container }) {18    // ...19  },20})

The example declares two settings for the product index to control result deduplication and provider-specific options.

Medusa Search accepts the following index settings:

Loading...

The example above sets distinct_attribute and provider_options. The section below covers typo_tolerance, which has a few properties of its own.

Typo Tolerance#

Medusa Search matches misspelled terms with a fuzzy index that it builds on every searchable field by default, so an index tolerates typos without declaring any setting. A query is the opposite: it matches terms exactly until you opt in, as explained in Opt a Query Into Typo Tolerance.

Set the typo_tolerance index setting to change the thresholds Medusa Search applies, to exempt a field, or to turn the fuzzy index off:

src/search/product.ts
1export const productIndex = defineSearchIndex({2  name: "product",3  entity: "product",4  fields: search.define({5    title: search.text().searchable(),6    sku: search.keyword().searchable(),7  }),8  settings: {9    typo_tolerance: {10      min_word_size_for_one_typo: 6,11      min_word_size_for_two_typos: 9,12      disabled_on_attributes: ["sku"],13    },14  },15  async *seed({ container }) {16    // ...17  },18})

The example tolerates a typo on a term of six characters or more, two typos from nine characters, and never fuzzy-matches sku. Refer to Index Settings for every property the setting accepts.

Opt a Query Into Typo Tolerance

The setting only prepares the index. Every query matches terms exactly until you pass the typo_tolerance search option:

Code
1const { data } = await query.search({2  entity: "product",3  fields: ["id", "title"],4  filters: { q: "sweatshrit" },5  search_options: {6    typo_tolerance: true,7  },8})

Medusa Search ranks a fuzzy match below every exact match, so enabling the option adds results rather than reordering the ones you already had.

Note: The option only widens a term match, so Medusa Search ignores it when the query passes no q filter, or when none of the fields it searches has a fuzzy index.

Field Options#

Use a field's providerOptions() modifier to configure how Medusa Search indexes that field.

For example:

src/search/product.ts
1export const productIndex = defineSearchIndex({2  fields: search.define({3    title: search.text().searchable().providerOptions({4      "search-medusa": {5        glob: true,6      },7    }),8    // ...9  }),10  // ...11})

Medusa Search reads the following field options:

Loading...

Set a Field's Language#

Medusa Search matches a searchable field term by term, and it doesn't stem those terms until you ask it to. Set the field's language along with stemming so a query matches the other forms of a word, which matters for a catalog whose titles and descriptions aren't in English.

For example, to analyze a Dutch title and a German one:

src/search/product.ts
1export const productIndex = defineSearchIndex({2  name: "product",3  entity: "product",4  fields: search.define({5    id: search.keyword().filterable(),6    title_nl: search.text().searchable().providerOptions({7      "search-medusa": {8        full_text_search: {9          language: "dutch",10          stemming: true,11          remove_stopwords: true,12        },13      },14    }),15    title_de: search.text().searchable().providerOptions({16      "search-medusa": {17        full_text_search: {18          language: "german",19          stemming: true,20          remove_stopwords: true,21        },22      },23    }),24  }),25  async *seed({ container, catchup, last_key: lastKey }) {26    const batchSize = 20027    let cursor = lastKey28
29    while (true) {30      const { data: products } = await container.query31        .graph({32          entity: "product",33          fields: [34            "id",35            "title",36            "updated_at",37            "deleted_at",38          ],39          filters: {40            ...(catchup41              ? { updated_at: { $gte: catchup.since } }42              : {}),43            ...(cursor ? { id: { $gt: cursor } } : {}),44          },45          pagination: {46            take: batchSize,47            order: { id: "ASC" },48          },49          withDeleted: !!catchup,50        })51
52      if (!products.length) {53        return54      }55
56      const live = products.filter((p) => !p.deleted_at)57      const gone = products.filter((p) => !!p.deleted_at)58      const ids = live.map((p) => p.id)59
60      const [nlTitles, deTitles] = await Promise.all(61        ["nl-NL", "de-DE"].map(async (locale) => {62          const { data } = await container.query.graph({63            entity: "product",64            fields: ["id", "title"],65            filters: { id: ids },66          }, { locale })67
68          return new Map(69            data.map((p) => [p.id, p.title])70          )71        })72      )73
74      yield [75        ...(live.length76          ? [{77              action: "upsert" as const,78              documents: live.map((product) => ({79                id: product.id,80                title_nl:81                  nlTitles.get(product.id) ?? product.title,82                title_de:83                  deTitles.get(product.id) ?? product.title,84              })),85            }]86          : []),87        ...(gone.length88          ? [{89              action: "delete" as const,90              filters: { id: gone.map((p) => p.id) },91            }]92          : []),93      ]94
95      if (products.length < batchSize) {96        return97      }98
99      cursor = products[products.length - 1].id100    }101  },102})

The seed function reads each page of products once per locale with Query's locale option, then writes each locale's title to its own field. It falls back to the product's stored title when a locale has no translation. It also pages by ID so an interrupted run resumes, and yields a delete write for the products the catch-up pass finds deleted.

A Dutch query for banken now matches a product whose title holds bank, and a German query for stühle matches Stuhl.

Medusa Search stems the following languages:

  • arabic (no stop-word list)
  • danish
  • dutch
  • english
  • finnish
  • french
  • german
  • greek (no stop-word list)
  • hungarian
  • italian
  • norwegian
  • portuguese
  • romanian (no stop-word list)
  • russian
  • spanish
  • swedish
  • tamil (no stop-word list)
  • turkish (no stop-word list)

remove_stopwords has no effect on the languages marked above with (no stop-word list). For a language written in Latin script with diacritics, such as French or Spanish, also set ascii_folding so a query without the accents still matches.

A field holds one language, since it holds one string. To search a catalog in more than one market, declare a field per locale as explained in Search Text in Multiple Locales, then pass the attributes_to_search_on search option to search the field of the shopper's locale.

Note: Configure the language on the index's fields rather than per query. Medusa Search rejects a query that passes the locales search option.

Search Query Options#

Pass provider_options to query.search options to change how Medusa Search runs that query.

For example:

Code
1const { data } = await query.search({2  entity: "product",3  fields: ["id", "title"],4  filters: { q: "sweatshirt" },5  search_options: {6    provider_options: {7      "search-medusa": {8        consistency: "eventual",9      },10    },11  },12})

Medusa Search reads one query option, consistency, which accepts the following values:

Loading...
Was this guide 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