docs/integrations/search/meilisearch.mdx
Meilisearch is a fast, open-source search engine that provides typo tolerance, relevance ranking, faceted filtering, and sub-50ms search responses. Spree includes a built-in Meilisearch search provider that replaces the default SQL-based search.
When to use Meilisearch:
When the default Database provider is sufficient:
brew install meilisearch
meilisearch
# Running at http://localhost:7700 — no API key needed for development
docker run -d --name meilisearch \
-p 7700:7700 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest
# Sign up at https://www.meilisearch.com/cloud
# Get your URL and API key from the dashboard
gem 'meilisearch', '>= 0.28'
bundle install
# .env
# Local development — no API key needed
MEILISEARCH_URL=http://localhost:7700
# Production — use the master key you set when starting Meilisearch
# MEILISEARCH_API_KEY=your-master-key-min-16-bytes
# config/initializers/spree.rb
Spree.search_provider = 'Spree::SearchProvider::Meilisearch'
rake spree:search:reindex
That's it. Your Store API now uses Meilisearch for all product search, filtering, and faceted navigation.
Products are indexed as one document per market × locale combination. Each document contains the product's name and description in that locale, the price in that market's currency, and all non-translated fields (categories, options, stock, tags).
For example, a store with a US market (USD, English) and an EU market (EUR, German/French) creates 3 index documents per product:
| Document ID | Name | Price | Locale | Currency |
|---|---|---|---|---|
prod_abc_en_USD | Blue Shirt | 29.99 | en | USD |
prod_abc_de_EUR | Blaues Hemd | 27.50 | de | EUR |
prod_abc_fr_EUR | Chemise Bleue | 27.50 | fr | EUR |
Products are only indexed for markets where they have a price. If a product has no EUR price, no EUR documents are created.
Translations use Mobility with fallback — if a product has no German translation, the default locale (English) is used.
Each document includes:
Preview what gets indexed:
# Rails console
product = Spree::Product.first
product.search_presentation
# Returns an array of documents — one per market × locale
GET /api/v3/store/products?q[search]=shirt&q[price_gte]=20&sort=price
store.products.active(currency).accessible_by(ability)locale='en' AND currency='USD' AND status='active' AND store_ids='{id}' AND not_discontinuedThe base filters ensure Meilisearch only returns products visible in the current locale and currency. The AR scope is a safety net — it should not filter out any additional products.
Each store gets its own Meilisearch index: {store_code}_products. For a store with code "my-store", the index name is my_store_products.
rake spree:search:reindex
product.add_to_search_index
product.remove_from_search_index
product.search_presentation
| Environment Variable | Default | Description |
|---|---|---|
MEILISEARCH_URL | http://localhost:7700 | Meilisearch server URL |
MEILISEARCH_API_KEY | nil | Meilisearch master key. Not needed for local development — only required when Meilisearch is started with MEILI_MASTER_KEY. |
The index is automatically configured with the correct filterable, sortable, and searchable attributes when you run rake spree:search:reindex.
The Meilisearch provider automatically configures these filterable attributes:
product_id — prefixed product ID (for bulk deletion of locale/currency variants)status — product status (always filtered to active for Store API)in_stock — boolean stock availabilitystore_ids — which stores the product belongs tolocale — document locale (filtered per request)currency — document currency (filtered per request)discontinue_on — Unix timestamp for discontinuation filteringprice — price in the document's currencycategory_ids — prefixed category IDstags — product tagsoption_value_ids — prefixed option value IDsname — sort alphabetically (locale-aware via per-locale documents)price — sort by price (currency-aware via per-currency documents)created_at — sort by creation dateavailable_on — sort by availability dateunits_sold_count — sort by best sellingname — product name (in document locale)description — product description (in document locale)sku — variant SKUoption_values — option value presentations (e.g., "Red", "Small")category_names — category namestags — product tagsThe Meilisearch provider is fully compatible with the existing Store API. No client-side changes are needed:
// Same SDK calls work with both Database and Meilisearch providers
const products = await client.products.list({
search: 'blue shirt',
price_gte: 20,
price_lte: 100,
in_stock: true,
sort: 'price',
limit: 12,
})
Locale and currency are automatically resolved from request headers (x-spree-locale, x-spree-currency) — no need to pass them as filter params.
Meilisearch supports tenant tokens for fine-grained access control. Use an admin key for indexing and a search key for queries.
For production deployments, consider Meilisearch Cloud for managed hosting, automatic scaling, and monitoring.