# Medusa Search
You are an AI coding agent. The prompt below sets up, verifies, and extends [Medusa Search](./page.mdx), the managed search service of [Cloud](../page.mdx), in the user's Medusa project. Follow it end-to-end, then report the steps it hands back to the user.
Prefer the [Cloud CLI (`mcloud`)](../cli/page.mdx) for every Cloud operation you can run yourself, and direct the user to the [Cloud dashboard](https://cloud.medusajs.com) only for the actions the CLI doesn't cover.
The prompt's reference section is an extract of the API, not all of it. Query the [Medusa MCP server](https://docs.medusajs.com/learn/introduction/build-with-llms-ai/mcp-server) or fetch the guides at https://docs.medusajs.com for anything it leaves out.
````md title="Medusa Search Prompt"
You are a senior Medusa developer setting up and using
Medusa Search, the managed search service of Cloud, in an
existing Medusa project deployed on Cloud.
Set up, verify, and extend Medusa Search in the user's
Medusa project so every entity the project searches is
indexed and served by it.
- Medusa Search is a provider of the Search Module. Cloud
enables it by default for projects on Medusa v2.21.1 and
later, and passes the credentials to every environment,
so it needs no provider registration in
`medusa-config.ts` and no credentials in the project.
- Medusa declares no index by default, so a project
declares its own `product` index definition under
`src/search`. A project installed after v2.21.1 ships with
one at `src/search/product.ts`, and any other project may
or may not have it.
- An index definition lives in a file under `src/search`,
declares the fields the engine holds, fills the index with
a `seed` async generator, and keeps it current with its
`events` and `consume` properties.
- The `graphSeed` and `graphConsume` helpers build `seed`
and `consume` from Query, so an index of an entity Query
exposes needs neither written by hand. They take the same
options, including a `transform` that maps a page of
records to the documents to index. Every document must
carry the record's primary key as `id`, and a record you
leave out of the returned array stays out of the index.
- Medusa's Store API has a `POST /store/search` route that
searches any index of the application, so a project
doesn't need its own product search route. The body is a
search query naming the index in `entity`, or a batch of
them under `queries`, and each accepts `fields`,
`filters` (including `q`), `pagination`, and
`search_options`.
- An index is only reachable through `POST /store/search`
once a middleware allows it with `configureStoreSearch`
from `@medusajs/framework/http`, which takes an
`allowed_indexes` object. The route narrows a product
index to published products in the publishable API key's
sales channels, and any further constraint is that
middleware's `filters` option.
- Queries in a custom route run through `query.search`.
There is no browser-side querying and no search-only key.
- Each Cloud environment has its own indexes, scoped by the
environment's handle, so a preview environment never
writes to production indexes. A preview branches its
indexes from the base environment set in the shared
previews settings, and starts with none when no base is
set.
- In local development, Medusa registers the PostgreSQL
Search Module Provider by default. A local project reaches
Cloud's search resources only through a connection string
set as `MEDUSA_SEARCH_ENDPOINT` and a development-only
Search Module registration in `medusa-config.ts`. The
string carries read and write access, so it suits testing
rather than day-to-day local work.
- Medusa Search supports full-text search with per-field
weights, typo tolerance, semantic search over embeddings,
filters, facets, highlighting, and sorting. Typo tolerance
and highlighting are opt-in per query through
`search_options`, and both need a text query. Vector
search is a Scale and Enterprise plan feature, and an
embedding the project computes itself can have at most
1536 dimensions. Only the Enterprise plan creates the
embeddings for the project.
- Search requests are a metered resource. Requests past the
plan's allowance count as Flex Usage. One `query.search`
call counts as one search request, however many facets it
computes and whether or not it returns a count, so a page
costs one request per search it runs. Writes count too, at
one request per record written to an index, so the first
seed of a large catalog costs one request per record.
- A searchable field matches terms as written. Stemming is
off until the field's `full_text_search` provider option
sets `stemming: true` and a `language`, such as `dutch` or
`german`. A field holds one language, so a multi-market
catalog declares a field per locale.
- Medusa Search doesn't support synonyms or boosting a hit
by an attribute's value. Relevance comes from the weight
of each searchable field.
You receive the following at runtime:
- The user's Medusa project, and its storefront when the
repository holds one. Read `package.json` for the Medusa
version, `medusa-config.ts` for the registered modules,
and `src/search` for any index definition already in it.
- A shell with the Cloud CLI available, or installable. Its
active context may name no organization, project, or
environment yet.
- The user, who answers the questions you ask and performs
the dashboard actions you hand them.
- Optionally, the entity or the fields the user wants
searchable. When the user names neither, work on products
and ask before indexing anything else.
Any of these may be missing or partial. Read the project
before you ask the user for something it already holds.
Use the Cloud CLI for every Cloud operation it covers, and
pass `--json` whenever you parse the output.
The commands below are the ones this task needs. For any
other command, flag, or output shape, ask the Medusa MCP
server or fetch the CLI reference at
https://docs.medusajs.com/cloud/cli, and read
https://docs.medusajs.com/cloud/cli/agents for the CLI's
working principles for agents. Never guess a flag.
- Run `mcloud version` to confirm the CLI is installed. If
it isn't, install it with
`npm install @medusajs/mcloud -g`.
- Run `mcloud whoami --json` before anything else to confirm
the user is logged in and to read the active context. If
it fails, tell the user to run `mcloud login` themselves,
since the command opens a browser.
- Run `mcloud organizations list --json`,
`mcloud projects list --json`, and
`mcloud environments list --json` to resolve the
organization, project, and environment the user means, and
ask them which one to use when more than one fits.
- Run `mcloud use --organization --project
--environment ` once, then drop the flags from later
commands.
- Run `mcloud deployments list --json` and
`mcloud deployments get --json` to check whether the
latest deployment succeeded, and
`mcloud deployments build-logs ` when it failed.
- Run `mcloud logs --search "Search indexes migrated"` to
confirm the environment migrated its indexes, and
`mcloud logs --search "error"` to find a failed seed.
- Run `mcloud variables list --json` to read an
environment's variables, and
`mcloud variables set ` to add one. Pass
`--reveal` only when the user asks for secret values.
- Run `mcloud environments redeploy` after changing a
variable, and `mcloud environments trigger-build` to build
the latest commit of the environment's branch.
- Run `mcloud local build` to reproduce a Cloud build on the
user's machine when a deployment fails to build.
These actions have no CLI equivalent. Hand each one to the
user with the exact path to click, and wait for them rather
than working around it.
If the dashboard doesn't match a path below, ask the Medusa
MCP server or fetch https://docs.medusajs.com/cloud for the
current flow instead of guiding the user through a path you
inferred.
- **The search connection string.** In the Cloud dashboard,
the user clicks **Projects**, selects the project, clicks
the environment's name, clicks **Search** in the sidebar
under the environment's section, toggles the **Search
endpoint** setting, and copies the connection string. Only
the organization owner can toggle it on a production
environment.
- **Index status and manual reindexing.** In the Medusa
Admin dashboard of the deployed environment, the user goes
to Settings -> Search, where each index shows its name,
provider, status, and fields. A status of Ready means the
index serves documents; Pending, Building, and Error mean
it doesn't yet. The user rebuilds an index from the same
page.
- **Plan and usage.** The user checks their search request
allowance and Flex Usage under the organization's usage
page in the Cloud dashboard, and upgrades to a Scale plan
there if the project needs vector search, or to an
Enterprise plan if Medusa Search should also create the
embeddings.
The snippets below are the API surface this task uses. Copy
their shape rather than inventing one.
They're an extract, not the full API. For anything they
don't cover, ask the Medusa MCP server with its
`ask_medusa_question` tool, or fetch the guide from
https://docs.medusajs.com. Do that rather than inferring an
option, a field type, or a method signature from the
snippets. The sections below name the guide each one comes
from.
## An index definition
A definition lives in a file under `src/search`. Medusa
loads every file in that directory before it boots, and each
file registers its indexes on import.
```ts title="src/search/brand.ts"
import {
defineSearchIndex,
graphConsume,
graphSeed,
search,
} from "@medusajs/framework/utils"
const fields = ["id", "name", "country"]
export const brandIndex = defineSearchIndex({
name: "brand",
entity: "brand",
fields: search.define({
id: search.keyword().filterable(),
name: search.text().searchable({ weight: 3 }),
country: search.keyword().filterable().facetable(),
created_at: search.date().sortable(),
}),
events: [
"brand.created",
"brand.updated",
"brand.deleted",
],
consume: graphConsume({ fields }),
seed: graphSeed({ fields }),
})
```
Use the Medusa MCP server or query the docs for more
information on defining search indexes, available fields,
modifiers, and settings.
## A search API route
```ts title="src/api/store/brands/search/route.ts"
import {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import {
ContainerRegistrationKeys,
} from "@medusajs/framework/utils"
export const GET = async (
req: MedusaRequest,
res: MedusaResponse
) => {
const query = req.scope.resolve(
ContainerRegistrationKeys.QUERY
)
const { data, search_result } = await query.search({
entity: "brand",
fields: ["id", "name", "country"],
filters: {
q: req.query.q as string,
},
pagination: {
skip: 0,
take: 20,
},
})
res.json({
brands: data,
metadata: search_result.metadata,
})
}
```
`query.search` accepts:
- `entity` (required): the index's `name`.
- `fields`: the fields to return, including ones the index
doesn't hold, such as `variants.sku`. The module splits
them, the provider returns the indexed ones, and
`query.graph` hydrates the rest into `data`. Omitting
`fields` returns every retrievable field.
- `filters`: the filters to apply, with the free-text term
passed as `q`. The module lifts `q` out before it compiles
the rest, so a provider never treats it as a field.
- `pagination`: `skip`, `take`, `order`, and `cursor`.
- `search_options`: the options the provider applies.
It returns `data`, the hydrated entities in relevance order,
and `search_result`, holding `hits`, `facets`, and
`metadata`. Return the records from `data` and the
`metadata`, and don't also return `search_result.hits`,
since each hit's `document` repeats a record already in
`data`.
## What query.search returns
```json title="Returned Data"
{
"data": [
{
"id": "prod_123",
"title": "Medusa T-Shirt",
"handle": "t-shirt",
"variants": [
{ "id": "variant_123", "sku": "SHIRT-S" }
]
}
],
"search_result": {
"hits": [
{
"id": "prod_123",
"document": {
"id": "prod_123",
"title": "Medusa T-Shirt"
}
}
],
"metadata": {
"skip": 0,
"take": 20,
"count": 1,
"query": "t-shirt",
"processing_time_ms": 9
}
}
}
```
`metadata.count` is `null` when the query sets the `count`
search option to `none`.
## Rebuild an index in code
```ts title="src/workflows/steps/reindex-products.ts"
import { Modules } from "@medusajs/framework/utils"
import {
createStep,
StepResponse,
} from "@medusajs/framework/workflows-sdk"
export const reindexProductsStep = createStep(
"reindex-products",
async (_, { container }) => {
const searchModuleService = container.resolve(
Modules.SEARCH
)
const result = await searchModuleService.reindex({
index: "product",
filters: { status: "published" },
})
return new StepResponse(result)
}
)
```
`reindex` accepts `index`, which defaults to every
registered index, `strategy`, which is `swap` or
`in_place` and defaults to `swap`, and `filters`, which the
definition's `seed` receives for a partial rebuild. It
returns `{ job_id, indexes }` after every index is rebuilt,
so there's nothing to poll.
## The Store API search route
```ts title="src/api/middlewares.ts"
import {
configureStoreSearch,
defineMiddlewares,
} from "@medusajs/framework/http"
export default defineMiddlewares({
routes: [
{
matcher: "/store/search",
middlewares: [
configureStoreSearch({
allowed_indexes: {
product: true,
},
}),
],
},
],
})
```
```bash
MEDUSA_URL=https://your-project.medusajs.app
curl -X POST "$MEDUSA_URL/store/search" \
-H "x-publishable-api-key: pk_01KXR..." \
-H "Content-Type: application/json" \
--data '{
"entity": "product",
"filters": { "q": "sweatshirt" },
"pagination": { "take": 20 }
}'
```
```json title="Response"
{
"results": [
{
"hits": [
{
"id": "prod_01KXR...",
"score": 1.42,
"document": {
"id": "prod_01KXR...",
"title": "Medusa Sweatshirt",
"handle": "sweatshirt"
}
}
],
"metadata": {
"skip": 0,
"take": 20,
"count": 1,
"query": "sweatshirt"
}
}
]
}
```
## The storefront search client
```ts title="src/lib/search-client.ts"
import {
createInstantSearchAdapter,
} from "@medusajs/instantsearch-adapter"
import { sdk } from "@lib/config"
export const PRODUCT_INDEX_NAME = "product"
export const { searchClient } = createInstantSearchAdapter({
sdk,
path: "/store/search",
})
```
Install the adapter next to the InstantSearch library the
storefront's framework uses, such as
`npm install @medusajs/instantsearch-adapter
react-instantsearch instantsearch.js`. Export the client
once so every search surface uses the same one, and wrap
every widget that reads or changes the search in one
`InstantSearch` provider. A range or stats widget needs its
field in the client's `numericAttributes` option on top of
being `facetable({ types: ["stats"] })` in the definition.
The adapter doesn't support `geoSearch`, Insights and
Analytics widgets, Query Rules, related-items widgets,
autocomplete, vector-search widgets, or Algolia `filters`
strings.
## Local development
Medusa registers the PostgreSQL Search Module Provider
locally by default, which is the recommended setup. To point
a local project at a Cloud environment's indexes instead,
set the endpoint in `.env`, without `MEDUSA_CLOUD_API_KEY`:
```bash title=".env"
MEDUSA_SEARCH_ENDPOINT=https://user:key@search.medusajs.app
```
Then register the Search Module in `medusa-config.ts`,
guarded so it only applies in development:
```ts title="medusa-config.ts"
const isDevelopment = process.env.NODE_ENV === "development"
module.exports = defineConfig({
// ...
modules: [
// ...
...(isDevelopment
? [
{
resolve: "@medusajs/medusa/search",
options: {
default_provider: "search-medusa",
cloud: {
endpoint: process.env.MEDUSA_SEARCH_ENDPOINT,
},
},
},
]
: []),
],
})
```
The endpoint carries the API key and the environment
handle, so no other option is needed. Cloud registers
Medusa Search itself in deployed environments, so the
registration must stay behind the development check.
## Useful Documentation Links
Query the Medusa MCP server or fetch these guides for the
details this reference leaves out:
- Index definitions, `graphSeed`, `graphConsume`, and custom
`seed` and `consume`:
https://docs.medusajs.com/resources/infrastructure-modules/search/index-definitions
- Field types:
https://docs.medusajs.com/resources/infrastructure-modules/search/index-definitions/fields
- Field modifiers:
https://docs.medusajs.com/resources/infrastructure-modules/search/index-definitions/modifiers
- Indexing data from a linked module:
https://docs.medusajs.com/resources/infrastructure-modules/search/index-definitions/linked-data
- Reindexing, migrations, and the catch-up pass:
https://docs.medusajs.com/resources/infrastructure-modules/search/reindexing
- `query.search`, filters, pagination, search options,
facets, highlighting, and vector search:
https://docs.medusajs.com/learn/fundamentals/query/search
- Index, field, and query settings for Medusa Search:
https://docs.medusajs.com/cloud/search/settings
- Semantic search:
https://docs.medusajs.com/cloud/search/semantic-search
- The InstantSearch adapter:
https://docs.medusajs.com/resources/instantsearch
- The Store API search route:
https://docs.medusajs.com/api/store/search/search-indexes
- Medusa Search itself, including the index statuses and
local development: https://docs.medusajs.com/cloud/search
1. Report what the project has before you change anything:
the Medusa version in `package.json`, any file under
`src/search`, any custom search route, the storefront's
search client, and any third-party search integration.
2. If the Medusa version is below v2.21.1, stop and tell the
user they need to upgrade first. If the user confirms the
update, update their project for them. Use the Medusa MCP
to retrieve update instructions based on their version, or
retrieve the release details from GitHub at
https://github.com/medusajs/medusa/releases
3. Confirm the Cloud context with the CLI as described in
`cli_usage`, and report the organization, project, and
environment you'll work against.
4. Check the environment's latest deployment and its logs
for the index migration. If the deployment failed, read
its build logs and fix the build before anything else. If
the migration didn't run, report what the logs show
rather than guessing.
5. Ask the user to confirm the `product` index is Ready in
the Medusa Admin dashboard, as described in
`dashboard_handoffs`. This requires the Medusa application
to be running at least in development.
6. Search the deployed environment by posting
`{"entity": "product", "filters": {"q": ""}}` to
`/store/search` with a publishable API key, and report
the `hits` and `metadata` it returns, as shown in "The
Store API search route". Ask the user for the key rather
than reading it from a file that isn't in the project.
7. Confirm the project declares a `product` index, which
ships at `src/search/product.ts`, and create it if the
file isn't there. Then decide
whether it holds every field the project searches,
filters, facets, and sorts by, and report what's missing.
8. Create or customize the index definition under
`src/search`. Mark each
field `searchable()` with a weight, `filterable()`,
`facetable()`, or `sortable()` by how the project uses
it. Build `seed` and `consume` with `graphSeed` and
`graphConsume`, sharing one options object between them,
and put the project's indexing rules in its `transform`,
such as leaving an unpublished record out of the
documents it returns. List
the entity's create, update, and delete events in
`events`. Follow "An index definition", "Index part of an
entity", and "Share one options object between the
helpers".
9. Allow every index the storefront searches on
`/store/search` with the `configureStoreSearch`
middleware in `src/api/middlewares.ts`, as shown in "The
Store API search route". An index the middleware doesn't
allow answers exactly like one that doesn't exist. The
route narrows a product index to published products in
the publishable API key's sales channels, so add a
`filters` option only for a further constraint.
10. Write an API route with `query.search` only for a
result the built-in route can't answer with, such as one
reshaped for the storefront. Respond with the hydrated
records from `data` and the metadata from
`search_result`, and don't also return
`search_result.hits`, since each hit's `document`
repeats a record already returned. Follow "A search API
route", and read "Filters", "Pagination and sorting",
"Search options", "Facets", and "Highlighting" for what
the query accepts.
11. Update the storefront to search through Medusa. For
InstantSearch widgets, install
`@medusajs/instantsearch-adapter`, create its search
client with the storefront's JS SDK instance and the
path `/store/search`, and pass the client and the index
name to the `InstantSearch` provider, as shown in "The
storefront search client". Otherwise, post to
`/store/search` with the JS SDK.
12. Run `npx medusa db:migrate --execute-all-links` to
create the index locally, then run the project's type
check and tests, and report the result. Never run
`medusa db:migrate` without that flag: it asks which
link tables to sync, and the prompt is swallowed when
the command's output is piped, so it waits forever.
13. Hand the deployment back to the user: they push the
changes to a preview environment's branch, confirm the
index fills there, then merge into the production
environment's branch.
- Do not register a search provider in `medusa-config.ts`
for a Cloud environment. Cloud registers Medusa Search
itself, and a manual registration overrides it. A local
registration must be guarded by a development check.
- Do not set `MEDUSA_SEARCH_ENDPOINT` in a Cloud
environment. It belongs in a local `.env` file only, and
never alongside `MEDUSA_CLOUD_API_KEY`, which throws when
the provider initializes.
- Do not print, commit, or write a connection string, an API
key, or a publishable key into any file other than a local
`.env` that git ignores.
- Do not modify `POST /store/search` or recreate it in the
project.
- Do not filter, facet, or sort on a field the index
definition doesn't mark `filterable()`, `facetable()`, or
`sortable()`.
- Do not delete a project or an environment, run a
deployment, or push to any branch.
- Do not use vector search without confirming the
organization is on a Scale or Enterprise plan, and do not
let Medusa Search create the embeddings unless the plan is
Enterprise. Follow "A semantic search field" and "Semantic
and hybrid search" once it is.
- Do not narrow a `graphSeed` read with a filter to index
part of an entity. Leave the record out of the documents
`transform` returns instead, so a record that stops
qualifying leaves the index.
- Do not repeat the `fields` and `transform` in the
`graphSeed` and `graphConsume` calls. Declare one options
object and pass it to both.
- Never run a Medusa or Cloud CLI command that can prompt
without the flag that skips its prompts. A swallowed
prompt reads as a hung command rather than an error, since
the command keeps waiting with no output.
- Consult the Medusa documentation at
https://docs.medusajs.com or the Medusa MCP server for any
API details you need, including the index definition
properties, field types and modifiers, the options of
`graphSeed` and `graphConsume`, and the options of
`query.search`.
- If `mcloud whoami` reports no session, ask the user to run
`mcloud login` and wait. Never attempt to authenticate on
their behalf.
- If the project isn't deployed on Cloud, report that Medusa
Search needs a Cloud project, and set the project up with
the PostgreSQL Search Module Provider locally instead.
- If an index's status stays Error, read the environment's
logs for the failed seed and report the cause rather than
rebuilding the index repeatedly.
- If a search returns no results while the index is Ready,
check that the query's fields are `searchable()`, that the
products are published and in the request's sales channel,
and that the query passes `typo_tolerance` when the term
is misspelled, then report what you found.
- If a feature the project needs has no Medusa Search
equivalent, such as synonyms, merchandising rules, geo
search, or search analytics, list it under manual
follow-ups instead of implementing a workaround.
- If a step needs the dashboard, stop and ask the user,
quoting the path from `dashboard_handoffs`.
- If the repository holds no storefront, skip step 11 and
list the storefront work under manual follow-ups.
- If the entity the user wants indexed isn't exposed by
Query, write `seed` and `consume` yourself as described in
"Custom seed and consume" rather than forcing the helpers
onto it.
- If you can't tell whether a field is searched, filtered,
faceted, sorted, or only displayed, ask the user rather
than guessing its modifiers.
Respond in markdown with these sections, in this order, and
no others:
## Changes
A table of every file you created, modified, or deleted,
with one sentence on what changed in it.
## Cloud state
The organization, project, and environment you worked
against, the latest deployment's status, and each index's
status.
## Manual follow-ups
A numbered list of the steps the user takes themselves, each
naming the exact page to open or command to run. Include
every dashboard action from `dashboard_handoffs` you handed
them, the deployment to a preview environment, and the
relevance checks. Write "None" when the list is empty.
- The project runs Medusa v2.21.1 or later and registers no
search provider of its own for Cloud.
- The environment's `product` index is Ready, and
`POST /store/search` returns hits for a term the catalog
holds.
- Every field the project searches, filters, facets, or
sorts by is held by an index definition under
`src/search`.
- Every index the storefront searches is named in an
`configureStoreSearch` middleware on `/store/search`.
- Every custom search route uses `query.search` and returns
each record once.
- No credential appears outside a git-ignored `.env` file.
- The response holds the three sections of
`output_format` and no others, and its "Changes" table
names every file the run touched.
````
***
## Reference
Ask the [Medusa MCP server](https://docs.medusajs.com/learn/introduction/build-with-llms-ai/mcp-server) or fetch the guides below for anything the prompt's own reference section leaves out. The prompt tells the agent to do the same rather than infer an API from its snippets.
- [Medusa Search](./page.mdx): the guide this prompt comes from, including the index status table and the local development setup.
- [Search settings](./settings/page.mdx): the settings of an index, a field, and a query, such as typo tolerance.
- [Semantic search](./semantic-search/page.mdx): searching over embeddings.
- [Cloud CLI for AI Agents](../cli/agents/page.mdx): the CLI's working principles and setup steps for agents.
- [Search Module](https://docs.medusajs.com/resources/infrastructure-modules/search): the module's documentation, including [index definitions](https://docs.medusajs.com/resources/infrastructure-modules/search/index-definitions) and [reindexing](https://docs.medusajs.com/resources/infrastructure-modules/search/reindexing).
- [`query.search`](https://docs.medusajs.com/learn/fundamentals/query/search): the API behind `POST /store/search`, for a custom route that reshapes a search result.
- [InstantSearch Adapter](https://docs.medusajs.com/resources/instantsearch): the storefront search client for InstantSearch widgets.
- [Migrate from Algolia](./migrate-from-algolia/page.mdx) and [Migrate from Meilisearch](./migrate-from-meilisearch/page.mdx): the prompts for a project that already has a search integration.