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-attributesrank 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+tsqueryis fine. MeiliSearch wins on typo tolerance, ranking quality, and millisecond response time.