Search Index Field Modifiers
In this guide, you'll learn about the modifiers you can chain on a field in a search index definition.
What is a Field Modifier?#
A modifier declares what the engine can do with a field. For example, searchable() includes the field in free-text matching, while filterable() allows filtering on it.
You can chain multiple modifiers to a field. A modifier that doesn't apply to a type isn't available on it, so an invalid combination fails to compile instead of failing when your application starts. Refer to Search Index Fields for the modifiers each type accepts.
searchable()#
Includes the field in free-text matching, which is what the q filter passed to query.search searches against. Available on keyword and text fields.
searchable modifier is never matched as free text, even when it holds a string.Pass a weight to boost how much the field contributes to a result's relevance. A field with a higher weight ranks its matches above equally good matches in a lower-weighted field. The weight defaults to 1, so searchable() and searchable({ weight: 1 }) behave the same.
1import {2 defineSearchIndex,3 search,4} from "@medusajs/framework/utils"5 6export const productIndex = defineSearchIndex({7 fields: search.define({8 title: search.text().searchable({ weight: 3 }),9 description: search.text().searchable({ weight: 1 }),10 // ...11 }),12 // ...13})
In this example, a product matching a query in its title outranks one matching only in its description.
- The PostgreSQL provider maps the weight onto PostgreSQL's four relevance labels. A weight of
3or more is the highest label,2the next, anything above1the next, and1or less the lowest. So there are four tiers, and raising a weight past3changes nothing. - The Medusa Search provider available for Cloud users multiplies the field's relevance by the weight, so a weight of
5does outrank a weight of3.
Refer to Medusa Search vs PostgreSQL for the other features providers treat differently.
filterable()#
Allows filters on the field. Available on every type except vector.
Filtering on a field without this modifier throws an error, since the Search Module validates the query against the definition before it reaches the provider.
sortable()#
Allows ordering results by the field through passing pagination.order to query.search. Available on every type except vector.
facetable()#
Allows facets on the field. A facet summarizes the matched documents by that field, which is what a storefront's filter sidebar shows. Available on every type except vector.
A query then requests the facet, and the result carries it in search_result.facets, keyed by field name:
The shape of each entry depends on the kind of facet, as shown in Facet Types.
Facet Types#
By default, the facet type depends on the field's type:
integer,float, anddatefields default to["range"].statsis never a default. Add it explicitly, since it's the least widely supported kind and implying it would make numeric fields unusable on a provider without aggregations.- Every other type defaults to
["value"].
You can also pass types to facetable() to choose which kinds of facet the field allows. Only integer, float, and date accept the types option. For example:
Each kind returns a different shape:
value: the distinct values of the field and how many documents hold each one. It can also returnother_countfor documents falling outside the returned values.
range: how many documents fall into each bucket the query defines. The result echoes each bucket'skey,from, andtowith itscount. Only available on theinteger,float, anddatefield types.
stats: one aggregate summary of the field across the matched documents, rather than a breakdown. Only available on theinteger,float, anddatefield types.
avg and sum are optional, since not every engine reports them.
facets: ["min_price"], requests a value facet, so it fails on a numeric field that only allows range.retrievable()#
Whether the field comes back on a hit. Most fields are retrievable by default, so you can omit this modifier unless you want to turn it off. Available on every type.
A vector field is the exception: it defaults to not retrievable, since a list of hundreds of numbers is rarely useful to the caller. Chain retrievable() on one to get the embedding back on every hit.
query.search reads this modifier to decide which requested fields the engine can serve, and which it must fetch with query.graph.object field's container is never retrievable, since the index only stores the sub-fields you declared. Request those sub-fields by their dotted path, such as brand.name, rather than requesting brand.array()#
Marks the field as holding a list of its type, rather than one value. Available on every type except vector.
A filter on an array field matches when any element matches. Faceting on one counts a document once per distinct element.
embed()#
Asks Medusa Search to create the field's embedding from text your documents pass on that same field, rather than having your documents supply the embedding. Available on a vector field only.
embed() takes no arguments:
With embed, your documents pass a string on the vector field, and a query can search it by passing raw text as search_options.vector.query. Refer to Let Medusa Search Create the Embedding for the full details.
providerOptions()#
Pass provider-specific options for one field, keyed by provider identifier. Use it to reach a feature the field definition doesn't model. Available on every type.
The options are passed to the targeted provider only, so you can pass different options to different providers for the same field.
An option only does something if the provider consumes it. A provider that doesn't read the providerOptions modifier ignores whatever you pass, so check the provider's documentation for the options it accepts. Of the providers Medusa ships, the Medusa Search provider available for Cloud users is the only one that reads them, and its guide lists the full set.
medusa-config.ts. Those configure the provider itself, whereas these travel with the index definition and apply to one field.