Meilisearch is an open-source search engine built for instant, typo-tolerant, “search as you type” experiences. It’s far simpler to run than Elasticsearch: a single binary, sensible defaults, and great relevance out of the box. Use it for product search in a shop, documentation search, or any app where LIKE '%term%' in SQL no longer cuts it.

When Meilisearch fits

  • Site and app search with typo tolerance, prefix search, facets and filters.
  • Datasets from thousands to tens of millions of documents.
  • Teams that want good search without tuning analyzers and mappings.

For log analytics, complex aggregations or very large clusters, Elasticsearch or OpenSearch are better fits. See Elasticsearch vs Meilisearch.

MEILI_KEY=$(openssl rand -base64 32)
echo "MEILI_MASTER_KEY=$MEILI_KEY" | sudo tee /etc/meili.env    # keep it safe
sudo chmod 600 /etc/meili.env

docker run -d --name meilisearch --restart unless-stopped \
  -p 127.0.0.1:7700:7700 \
  --env-file /etc/meili.env \
  -e MEILI_ENV=production \
  -v meili-data:/meili_data \
  getmeili/meilisearch:v1.12

(Use the current version tag.)

  • MEILI_ENV=production requires a master key and disables the built-in preview UI.
  • Bind to localhost, and expose it through a reverse proxy with HTTPS (below).

Or the binary with systemd

curl -L https://install.meilisearch.com | sh
sudo mv ./meilisearch /usr/local/bin/

Run it as a dedicated user with --master-key, --env production and --db-path /var/lib/meilisearch/data.ms under a systemd unit. See systemd unit files.

Add documents

Meilisearch takes JSON documents with a primary key:

curl -X POST 'http://127.0.0.1:7700/indexes/products/documents?primaryKey=id' \
  -H "Authorization: Bearer $MEILI_KEY" -H 'Content-Type: application/json' \
  --data-binary @products.json

products.json:

[
  { "id": 1, "name": "Wireless Keyboard", "brand": "Acme", "price": 49, "category": "accessories" },
  { "id": 2, "name": "Mechanical Keyboard", "brand": "KeyCo", "price": 129, "category": "accessories" }
]

Indexing is asynchronous. Check task status with GET /tasks.

curl 'http://127.0.0.1:7700/indexes/products/search' \
  -H "Authorization: Bearer $MEILI_KEY" -H 'Content-Type: application/json' \
  --data '{ "q": "keybaord", "limit": 10 }'

The typo “keybaord” still finds keyboards.

Configure relevance and filters

curl -X PATCH 'http://127.0.0.1:7700/indexes/products/settings' \
  -H "Authorization: Bearer $MEILI_KEY" -H 'Content-Type: application/json' \
  --data '{
    "searchableAttributes": ["name", "brand", "description"],
    "filterableAttributes": ["category", "brand", "price"],
    "sortableAttributes": ["price"],
    "displayedAttributes": ["id", "name", "brand", "price"]
  }'

Then filter and facet:

{ "q": "keyboard", "filter": "category = accessories AND price < 100", "facets": ["brand"], "sort": ["price:asc"] }
  • searchableAttributes order matters: earlier fields weigh more.
  • Synonyms, stop words, ranking rules and typo tolerance are all adjustable in settings.

API keys: never expose the master key

The master key is for administration only. Create restricted keys:

  • a search-only key for the frontend (browser JavaScript), limited to specific indexes;
  • an admin key for your backend to add or update documents.
curl -X POST 'http://127.0.0.1:7700/keys' \
  -H "Authorization: Bearer $MEILI_KEY" -H 'Content-Type: application/json' \
  --data '{ "description": "frontend search", "actions": ["search"], "indexes": ["products"], "expiresAt": null }'

For per-user restrictions (multi-tenant data), use tenant tokens generated by your backend.

HTTPS with Nginx

server {
    server_name search.example.com;
    location / {
        proxy_pass http://127.0.0.1:7700;
        proxy_set_header Host $host;
        client_max_body_size 100m;      # for document uploads
    }
    # listen 443 ssl + certbot
}

See Let’s Encrypt for Nginx. Add CORS settings if the browser calls it directly from another domain.

Keep data in sync

  • Push changes from your app (on create, update and delete) through the API. Official SDKs exist for JS, PHP, Python, Ruby, Go and more.
  • Integrations: Laravel Scout, Strapi, WordPress plugins, and Firebase and other connectors.
  • Batch-reindex nightly as a safety net.

Resources and backups

  • RAM: Meilisearch uses memory-mapped files. Give it RAM comparable to the index size for best speed. Indexing large datasets is CPU-intensive.
  • Disk: the index is larger than the raw JSON. Plan 2–5× the raw size.
  • Snapshots/dumps: POST /dumps creates a portable dump (used for upgrades). Scheduled snapshots are available via launch options. Copy them off the server. See backup strategy.
  • Upgrades between major versions may need a dump and re-import. Read the release notes.

Quick reference

Task Endpoint
Add documents POST /indexes/{uid}/documents
Search POST /indexes/{uid}/search
Settings PATCH /indexes/{uid}/settings
Task status GET /tasks
Create key POST /keys
Dump POST /dumps
Health GET /health

Related: Typesense and MySQL EXPLAIN.

  • Search
  • Self-Hosting
  • Databases