Skip to main content
Mercur ships a Search module in @mercurjs/core, modeled on Medusa’s pluggable-provider pattern (like the file and notification modules). The module owns a stable contract — index, remove, search — and delegates storage to exactly one active provider. Out of the box that provider is Orama: an in-process, in-memory index that needs zero external infrastructure.
The search module is a release-candidate feature and its surface may still evolve before the stable release.
Everything above the provider is provider-agnostic. Subscribers index and remove documents on product, offer, and seller events; the store route calls search.search(query); boot reindexing is event-driven. Swapping Orama for Algolia or Meilisearch means changing one provider registration — no changes to subscribers, the store route, or your storefront.

What gets indexed

Products and offers are searchable. Offers are indexed as per-offer documents — one hit per vendor listing — each carrying the offer’s scoped price, so search results price identically to GET /store/offers. Index updates are driven by subscribers on product, offer, and seller events; you never call index() from application code.

Searching from the storefront

The Store API exposes one endpoint:
curl -X POST "http://localhost:9000/store/search" \
  -H "x-publishable-api-key: <key>" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "t-shirt",
    "limit": 12,
    "offset": 0,
    "region_id": "reg_123"
  }'
FieldDescription
qSearch query (defaults to "" — browse mode)
limit / offsetPagination (limit 1–100, default 12)
region_id, country_code, provincePricing context — the provider projects the matching price onto each hit’s calculated_price
filtersPassed straight to the active provider, which owns its shape
The response returns hits, count, and facets.

The default provider: Orama

The bundled search-orama provider keeps the index in RAM inside the API process. That means:
  • Zero setup — no accounts, keys, or services. Search works on a fresh project.
  • Boot reindex — an in-memory index is empty after a restart, so the module emits a reindex event on application start and a subscriber rebuilds the index from the database.
In-memory search fits development and small-to-medium catalogs. For large catalogs or multi-instance deployments, use a persistent provider — the index then lives in the external service and the boot reindex is unnecessary.

Swapping in Algolia or Meilisearch

Ready-made provider integrations ship as blocks:
bunx @mercurjs/cli@rc add algolia
# or
bunx @mercurjs/cli@rc add meilisearch
ProviderHostingBest for
Orama (default)In-processZero-infra development and smaller catalogs
AlgoliaManaged SaaSFastest setup, hosted relevance tuning
MeilisearchSelf-hosted or cloudFull control, open source
Each block installs the provider source and prints the configuration steps (API keys, module options). Follow the block’s post-install docs to register the provider and set environment variables.

Writing your own provider

A provider extends AbstractSearchProvider from @mercurjs/core and implements three methods:
import { AbstractSearchProvider } from "@mercurjs/core/modules/search"
import { SearchDoc, SearchQueryBase, SearchResults } from "@mercurjs/types"

export class MySearchProvider extends AbstractSearchProvider {
  static identifier = "my-search"

  async index(docs: SearchDoc[]): Promise<void> { /* upsert documents */ }
  async remove(ids: string[]): Promise<void> { /* delete by id */ }
  async search(query: SearchQueryBase): Promise<SearchResults> { /* query + facets */ }
}
Because subscribers and the store route only talk to the module contract, your provider slots in without touching anything else. Use the Algolia or Meilisearch block source as a working reference.

Verify

  1. Start a fresh project and POST /store/search with an empty q — published products return without any search service configured (that’s Orama).
  2. Publish a product and search for its title — it appears without a manual reindex (subscribers indexed it).
  3. Restart the API and search again — results still return (the boot reindex rebuilt the in-memory index).

FAQ

Only one provider is active at a time — the module has no multi-provider machinery. But because the provider is chosen by configuration, you can register different providers per environment (default Orama in development, Algolia in production) without touching any other code.
Normally no — subscribers keep the index current, and in-memory providers rebuild on boot. A manual full reindex is only interesting after bulk data changes outside normal flows (e.g. direct DB imports); persistent providers ignore the boot event entirely.
Because pricing and availability are per offer, not per product. One hit per vendor listing lets results carry the correct calculated_price for each seller — the same numbers as GET /store/offers — instead of an ambiguous product-level price.

Next steps

Offers

Why offers are indexed per seller with scoped prices.

Add a feature with a block

How provider blocks are installed and configured.