Skip to content

Meilisearch Search Engine

The stoa-plugin-meilisearch plugin replaces the built-in PostgreSQL full-text search with Meilisearch — providing typo-tolerant, fast, and relevant search with multi-locale support. It implements the sdk.SearchPlugin interface, so the switch is fully transparent: the existing /api/v1/store/search endpoint works unchanged, and the Storefront search page requires no modifications.

How it works

Frontend → GET /api/v1/store/search → search.Handler → search.Engine

                                          ┌───────────────┼──────────────┐
                                          │ PostgresEngine │ SDKAdapter  │
                                          │ (default)      │ → Plugin    │
                                          └────────────────┴─────────────┘

When the Meilisearch plugin is installed, Stoa detects it at startup and routes all search queries through Meilisearch instead of PostgreSQL. When the plugin is removed, search automatically falls back to PostgreSQL — no downtime, no code changes.

Automatic Sync

Product and category changes are automatically synchronized to Meilisearch via entity lifecycle hooks:

  • Product create/update → all locale variants indexed
  • Product delete → all locale variants removed
  • Category create/update → all locale variants indexed
  • Category delete → all locale variants removed

Hook handlers run asynchronously (background goroutine with 30s timeout) so they never block the API request.

Document Design

Each entity is stored as one document per locale:

  • Document ID: {entity_id}_{locale} (e.g. 550e8400-e29b_de-DE)
  • Products Index: {prefix}_products
  • Categories Index: {prefix}_categories
IndexSearchableFilterableSortable
{prefix}_productsname, description, skulocale, active, category_ids, price_grossprice_gross, created_at, name
{prefix}_categoriesname, descriptionlocale, active, parent_idposition, name

Prerequisites

  • Meilisearch server v1.6+ running and accessible from the Stoa instance

Quick start with Docker:

bash
docker run -d --name meilisearch \
  -p 7700:7700 \
  -e MEILI_MASTER_KEY=master-key \
  -v meili-data:/meili_data \
  getmeili/meilisearch:latest

Installation

bash
stoa plugin install meilisearch

Or manually via Go:

bash
go get github.com/stoa-hq/stoa-plugins/meilisearch@latest

Configuration

Add a meilisearch section to your config.yaml:

yaml
plugins:
  meilisearch:
    host: "http://localhost:7700"
    api_key: "master-key"
    index_prefix: "stoa"        # optional, default: stoa
    sync_on_start: true         # optional, default: true
    batch_size: 500             # optional, default: 500
KeyTypeDefaultDescription
hoststring— (required)Meilisearch server URL
api_keystring— (required)Meilisearch API key (master or search key)
index_prefixstringstoaPrefix for Meilisearch index names
sync_on_startbooltrueRun a full sync when Stoa starts
batch_sizeint500Documents per batch during bulk indexing

Index Prefix

Use a unique prefix per environment (e.g. stoa_dev, stoa_prod) if multiple Stoa instances share the same Meilisearch server.

Admin API

Trigger Full Reindex

Re-indexes all products and categories (including inactive ones). Useful after bulk imports or schema changes. The storefront filters by active = true at query time, so inactive entities won't appear in customer-facing search results.

POST /api/v1/admin/meilisearch/reindex
Authorization: Bearer <admin-token>

Response: 202 Accepted

json
{
  "data": {
    "status": "accepted",
    "message": "reindex started"
  }
}

The reindex runs in a background goroutine but waits for each Meilisearch task to complete before proceeding to the next batch. Progress is logged to the server console — look for "products indexed" count=N and "categories indexed" count=N.

Docker Compose

yaml
services:
  stoa:
    image: ghcr.io/stoa-hq/stoa:latest
    ports:
      - "8080:8080"
    volumes:
      - ./config.yaml:/app/config.yaml
    depends_on:
      - postgres
      - meilisearch

  meilisearch:
    image: getmeili/meilisearch:latest
    ports:
      - "7700:7700"
    environment:
      MEILI_MASTER_KEY: "master-key"
    volumes:
      - meili-data:/meili_data

  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: stoa
      POSTGRES_USER: stoa
      POSTGRES_PASSWORD: stoa
    volumes:
      - pg-data:/var/lib/postgresql/data

volumes:
  meili-data:
  pg-data:

Verification

After starting Stoa with the plugin:

  1. Check the logs for "using plugin search engine" and "meilisearch plugin initialised"
  2. If sync_on_start is enabled, look for "initial sync completed"
  3. Test the search endpoint:
bash
curl "http://localhost:8080/api/v1/store/search?q=test&locale=de-DE"
  1. Create or update a product — it should appear in search results within seconds

Uninstalling

Remove the plugin and restart Stoa. Search automatically falls back to PostgreSQL full-text search.

bash
stoa plugin remove meilisearch

Meilisearch data

Removing the plugin does not delete data from Meilisearch. To clean up, either delete the Meilisearch indexes manually or remove the Docker volume.

Troubleshooting

IssueSolution
missing config section "meilisearch"Add the meilisearch block under plugins: in config.yaml
connection refused in logsVerify that Meilisearch is running and reachable at the configured host
Search returns empty resultsTrigger a manual reindex via the admin API. Check logs for "0 products found" — this means no products with translations exist in the database
Stale results after product updateCheck that the hook handlers are running (look for sync logs)
Reindex reports success but index emptyEnsure products have at least one translation row (product_translations). Entities without translations are skipped because of the JOIN

Released under the APACHE 2.0 License.