Install

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

# Or container
docker run -d \
    --name meilisearch \
    --restart unless-stopped \
    -p 7700:7700 \
    -v meili-data:/meili_data \
    -e MEILI_MASTER_KEY="<long-random-string>" \
    -e MEILI_ENV=production \
    getmeili/meilisearch:v1.11

The MEILI_MASTER_KEY is the bootstrap admin credential — required when MEILI_ENV=production. Use it to mint scoped API keys (admin / search / specific-index-only).

systemd

sudo useradd -r -s /sbin/nologin meili
sudo mkdir -p /var/lib/meilisearch
sudo chown meili:meili /var/lib/meilisearch

sudo tee /etc/systemd/system/meilisearch.service <<'EOF'
[Unit]
Description=Meilisearch
After=network-online.target

[Service]
User=meili
Group=meili
Environment=MEILI_DB_PATH=/var/lib/meilisearch
Environment=MEILI_HTTP_ADDR=127.0.0.1:7700
Environment=MEILI_ENV=production
EnvironmentFile=/etc/default/meilisearch
ExecStart=/usr/local/bin/meilisearch
Restart=always
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF
# /etc/default/meilisearch  (chmod 600)
MEILI_MASTER_KEY=<long-random-string>
sudo systemctl enable --now meilisearch

Reverse proxy

# Caddy
search.example.com {
    reverse_proxy 127.0.0.1:7700
}

Index documents

MeiliSearch is schemaless — throw JSON documents at it, search begins working. Each document needs a primary key (auto-detected if a field named id, uid, or similar exists).

# Add 3 documents to an index called "movies"
curl -X POST 'https://search.example.com/indexes/movies/documents' \
    -H 'Authorization: Bearer <admin-or-write-api-key>' \
    -H 'Content-Type: application/json' \
    -d '[
      { "id": 1, "title": "The Matrix",      "year": 1999, "genre": ["scifi", "action"] },
      { "id": 2, "title": "Dune Part Two",   "year": 2024, "genre": ["scifi", "drama"] },
      { "id": 3, "title": "Inception",       "year": 2010, "genre": ["scifi", "thriller"] }
    ]'

# Returns an async task; check its status
curl 'https://search.example.com/tasks/1' \
    -H 'Authorization: Bearer <api-key>'

Search

curl 'https://search.example.com/indexes/movies/search?q=matriks' \
    -H 'Authorization: Bearer <search-api-key>'

"matriks" matches "The Matrix" — MeiliSearch tolerates 1–2 typos for words of typical length. The response includes processingTimeMs (typically <5 ms for in-memory indexes) and highlighted matches when requested.

Configure searchable / filterable / sortable

# Default: ALL fields are searchable. To restrict:
curl -X POST 'https://search.example.com/indexes/movies/settings/searchable-attributes' \
    -H 'Authorization: Bearer <admin-key>' \
    -H 'Content-Type: application/json' \
    -d '["title", "genre"]'

# Mark fields as filterable (required before filtering on them)
curl -X POST 'https://search.example.com/indexes/movies/settings/filterable-attributes' \
    -H 'Authorization: Bearer <admin-key>' \
    -H 'Content-Type: application/json' \
    -d '["genre", "year"]'

# Now this works
curl 'https://search.example.com/indexes/movies/search?q=&filter=genre%20IN%20%5B%27scifi%27%5D%20AND%20year%20%3E%202000'

# Sortable fields
curl -X POST '.../settings/sortable-attributes' -d '["year"]'

Ranking rules

MeiliSearch's ranking is a configurable list applied left-to-right; documents that score equal on one rule are tied-broken by the next. Default:

[ "words", "typo", "proximity", "attribute", "sort", "exactness" ]
  • words — more query words matched first
  • typo — fewer typos in the match
  • proximity — query words closer together in the matched document
  • attribute — matches in earlier searchable-attributes rank higher (so title beats body)
  • sort — per-query sort takes effect here
  • exactness — exact-word matches rank above prefix / partial

To add "popular movies first," append a custom rule on a numeric field:

curl -X POST '.../settings/ranking-rules' \
    -H 'Authorization: Bearer <admin-key>' \
    -H 'Content-Type: application/json' \
    -d '["words", "typo", "proximity", "attribute", "sort", "exactness", "popularity:desc"]'

Frontend: instant-meilisearch + the React/Vue UI components

The Algolia-compatible client (instant-meilisearch) lets you reuse Algolia's polished React InstantSearch, Vue InstantSearch, etc. components against MeiliSearch:

import { InstantSearch, SearchBox, Hits } from "react-instantsearch";
import { instantMeiliSearch } from "@meilisearch/instant-meilisearch";

const { searchClient } = instantMeiliSearch(
    "https://search.example.com",
    "<search-only-api-key>"
);

function App() {
  return (
    <InstantSearch indexName="movies" searchClient={searchClient}>
      <SearchBox />
      <Hits />
    </InstantSearch>
  );
}

As-you-type results, faceted filters, and pagination ship out of the box.

Vector search (semantic)

MeiliSearch 1.x supports vector search alongside lexical:

# Configure embedders
curl -X POST '.../settings/embedders' \
    -H 'Content-Type: application/json' \
    -d '{
      "default": {
        "source": "openAi",
        "apiKey": "sk-...",
        "model": "text-embedding-3-small",
        "documentTemplate": "A movie titled {{doc.title}} ({{doc.year}}): {{doc.description}}"
      }
    }'

# Now searches can include semantic ranking
curl '.../search' -X POST -d '{
    "q": "movies about hackers in a simulated reality",
    "hybrid": { "semanticRatio": 0.5, "embedder": "default" }
}'

The combination of lexical and semantic is usually better than either alone — lexical catches exact-word matches, semantic catches synonyms and reformulations.

API keys with scope

curl -X POST 'https://search.example.com/keys' \
    -H 'Authorization: Bearer <master-key>' \
    -H 'Content-Type: application/json' \
    -d '{
      "description":  "Frontend search key",
      "actions":      ["search"],
      "indexes":      ["movies"],
      "expiresAt":    "2026-12-31T00:00:00Z"
    }'

Search-only keys are safe to embed in client-side code — they can't modify anything.

Backups

MeiliSearch has a built-in dump format:

curl -X POST 'https://search.example.com/dumps' \
    -H 'Authorization: Bearer <master-key>'

The dump lands under dumps/ as a single file. Restore via --import-dump path.dump on a fresh instance. Combine with a restic job on the data directory for offsite.

MeiliSearch vs alternatives

  • Typesense — very similar shape; pick on community / ecosystem fit. Both are great choices.
  • Elasticsearch / OpenSearch — vastly more features (aggregations, complex queries, distributed analytics) but the operational and query-DSL complexity is an order of magnitude higher. Reach for them when you outgrow MeiliSearch on data volume or query shape, not before.
  • Postgres full-text search — if the data already lives in Postgres and queries are simple, tsvector + tsquery is fine. MeiliSearch wins on typo tolerance, ranking quality, and millisecond response time.