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.
Index Settings#
Set the settings property of an index definition to configure how Medusa Search treats the whole index.
For example:
export const productIndex = defineSearchIndex({
name: "product",
entity: "product",
fields: search.define({
id: search.keyword().filterable(),
title: search.text().searchable(),
brand: search.keyword().filterable(),
}),
settings: {
distinct_attribute: "brand",
provider_options: {
"search-medusa": {
distance_metric: "cosine_distance",
},
},
},
async *seed({ container }) {
// ...
},
})
The example declares two settings for the product index to control result deduplication and provider-specific options.
Medusa Search accepts the following index settings:
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:
export const productIndex = defineSearchIndex({
name: "product",
entity: "product",
fields: search.define({
title: search.text().searchable(),
sku: search.keyword().searchable(),
}),
settings: {
typo_tolerance: {
min_word_size_for_one_typo: 6,
min_word_size_for_two_typos: 9,
disabled_on_attributes: ["sku"],
},
},
async *seed({ container }) {
// ...
},
})
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.
Typo Tolerance Minimums
Medusa Search only matches a misspelled term when the term is long enough, so the thresholds have hard minimums:
min_word_size_for_one_typomust be at least6.min_word_size_for_two_typosmust be at least9, and it can't be lower thanmin_word_size_for_one_typo.
The defaults already equal these minimums, so you can only raise the thresholds, not lower them.
Medusa validates the thresholds when it runs the index migrations, not when it builds your application. If a threshold is below its minimum, the build succeeds, but the index migration step throws an error that fails the deployment. For example, setting min_word_size_for_two_typos to 8 throws the following error:
Medusa search requires settings.typo_tolerance.min_word_size_for_two_typos to be at least 9 to tolerate 2 typos, got 8
A min_word_size_for_one_typo lower than 6 throws the same error for that setting, and a min_word_size_for_two_typos lower than min_word_size_for_one_typo also fails the index migration step. To fix the deployment, raise the threshold to at least its minimum, then push and deploy the change.
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:
const { data } = await query.search({
entity: "product",
fields: ["id", "title"],
filters: { q: "sweatshrit" },
search_options: {
typo_tolerance: true,
},
})
Medusa Search ranks a fuzzy match below every exact match, so enabling the option adds results rather than reordering the ones you already had.
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:
export const productIndex = defineSearchIndex({
fields: search.define({
title: search.text().searchable().providerOptions({
"search-medusa": {
glob: true,
},
}),
// ...
}),
// ...
})
Medusa Search reads the following field options:
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:
export const productIndex = defineSearchIndex({
name: "product",
entity: "product",
fields: search.define({
id: search.keyword().filterable(),
title_nl: search.text().searchable().providerOptions({
"search-medusa": {
full_text_search: {
language: "dutch",
stemming: true,
remove_stopwords: true,
},
},
}),
title_de: search.text().searchable().providerOptions({
"search-medusa": {
full_text_search: {
language: "german",
stemming: true,
remove_stopwords: true,
},
},
}),
}),
async *seed({ container, catchup, last_key: lastKey }) {
const batchSize = 200
let cursor = lastKey
while (true) {
const { data: products } = await container.query
.graph({
entity: "product",
fields: [
"id",
"title",
"updated_at",
"deleted_at",
],
filters: {
...(catchup
? { updated_at: { $gte: catchup.since } }
: {}),
...(cursor ? { id: { $gt: cursor } } : {}),
},
pagination: {
take: batchSize,
order: { id: "ASC" },
},
withDeleted: !!catchup,
})
if (!products.length) {
return
}
const live = products.filter((p) => !p.deleted_at)
const gone = products.filter((p) => !!p.deleted_at)
const ids = live.map((p) => p.id)
const [nlTitles, deTitles] = await Promise.all(
["nl-NL", "de-DE"].map(async (locale) => {
const { data } = await container.query.graph({
entity: "product",
fields: ["id", "title"],
filters: { id: ids },
}, { locale })
return new Map(
data.map((p) => [p.id, p.title])
)
})
)
yield [
...(live.length
? [{
action: "upsert" as const,
documents: live.map((product) => ({
id: product.id,
title_nl:
nlTitles.get(product.id) ?? product.title,
title_de:
deTitles.get(product.id) ?? product.title,
})),
}]
: []),
...(gone.length
? [{
action: "delete" as const,
filters: { id: gone.map((p) => p.id) },
}]
: []),
]
if (products.length < batchSize) {
return
}
cursor = products[products.length - 1].id
}
},
})
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.
Supported Languages in Medusa Search#
Medusa Search stems the following languages:
arabic(no stop-word list)danishdutchenglishfinnishfrenchgermangreek(no stop-word list)hungarianitaliannorwegianportugueseromanian(no stop-word list)russianspanishswedishtamil(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.
locales search option.Search Query Options#
Pass provider_options to query.search options to change how Medusa Search runs that query.
For example:
const { data } = await query.search({
entity: "product",
fields: ["id", "title"],
filters: { q: "sweatshirt" },
search_options: {
provider_options: {
"search-medusa": {
consistency: "eventual",
},
},
},
})
Medusa Search reads one query option, consistency, which accepts the following values: