Overview
Qdrant is a vector similarity search engine. Meilisearch combines full-text search with vector search through its hybrid search feature, letting you replace a separate keyword search engine and vector database with a single system. This guide walks you through exporting points from a Qdrant collection and importing them into Meilisearch using a script in JavaScript, Python, or Ruby. You can also skip directly to the finished script. The migration process consists of four steps:- Export your data from Qdrant
- Prepare your data for Meilisearch
- Import your data into Meilisearch
- Configure embedders and index settings
This guide includes examples in JavaScript, Python, and Ruby. The packages used:
- JavaScript:
@qdrant/js-client-rest1.x,meilisearch(compatible with Meilisearch v1.0+) - Python:
qdrant-client1.x,meilisearch - Ruby:
qdrant-ruby,meilisearch
Export your Qdrant data
Initialize project
Install dependencies
Create Qdrant client
You need your Qdrant host URL and optionally an API key if your instance requires authentication.QDRANT_URL with your Qdrant instance URL (for example, http://localhost:6333) and provide your API key if required.
Fetch data from Qdrant
Use the Scroll API to paginate through all points in a collection. This retrieves both payload data and vectors.YOUR_COLLECTION_NAME with the name of the Qdrant collection you want to migrate.
Set
with_vectors: true if you want to keep your existing vectors. If you plan to let Meilisearch re-embed your documents using a configured embedder, you can set this to false to speed up the export.Prepare your data
Qdrant points contain anid, a payload (key-value data), and one or more vectors. You need to extract the payload fields as top-level document fields for Meilisearch.
Choose your vector strategy
Before preparing your data, decide how you want to handle vectors:- Option A: Let Meilisearch re-embed (recommended) — Configure an embedder in Meilisearch and let it generate vectors automatically from your document content. This is simpler and keeps your vectors in sync with your data.
- Option B: Keep existing vectors — Include your Qdrant vectors in the
_vectorsfield of each document using auserProvidedembedder. This avoids re-embedding costs but requires you to manage vector updates yourself.
Transform documents
Handle geo data
If your Qdrant payloads containgeo fields (objects with lat and lon), convert them to Meilisearch’s _geo format:
Import your data into Meilisearch
Create Meilisearch client
Create a Meilisearch client by passing the host URL and API key of your Meilisearch instance. The easiest option is to use the automatically generated admin API key.MEILI_HOST, MEILI_API_KEY, and MEILI_INDEX_NAME with your Meilisearch host URL, API key, and target index name. Meilisearch will create the index if it doesn’t already exist.
Upload data to Meilisearch
Use the Meilisearch client to upload all records in batches of 100,000.Finished script
Configure your index settings
After importing your data, you need to configure Meilisearch to handle vector search. You also gain access to full-text search, typo tolerance, faceting, and other features that work automatically.Configure embedders
One of the biggest differences between Qdrant and Meilisearch is how they handle vectors. With Qdrant, your application must compute vectors before indexing and searching. With Meilisearch, you configure an embedder once and Meilisearch handles all embedding automatically — both at indexing time and at search time. This means you can remove all embedding logic from your application code. Instead of calling an embedding API, computing vectors, and sending them to your search engine, you simply send documents and text queries to Meilisearch. Configure an embedder source such as OpenAI, HuggingFace, or a custom REST endpoint:documentTemplate controls what text is sent to the embedding model. Adjust it to match the fields in your documents. Meilisearch will automatically embed all existing documents and keep vectors up to date as you add, update, or delete documents.
For more options including HuggingFace models, Ollama, and custom REST endpoints, see configuring embedders.
Alternative: use existing vectors from Qdrant
Alternative: use existing vectors from Qdrant
If you prefer to keep your existing Qdrant vectors instead of re-embedding, you can export them (set Replace
with_vectors: true in the migration script) and configure a userProvided embedder:1536 with the vector dimension used in your Qdrant collection. With this approach, you remain responsible for computing and providing vectors when adding or updating documents. You also need to compute query vectors client-side when searching.If your Qdrant collection uses named vectors, create a separate embedder for each vector name. The embedder names in Meilisearch must match the keys used in the _vectors field of your documents.Configure filterable and sortable attributes
In Qdrant, payload indexes must be created explicitly for filtering. In Meilisearch, configurefilterableAttributes and sortableAttributes for the fields you want to filter and sort on:
What you gain
Migrating from Qdrant to Meilisearch gives you several features that work out of the box:- No more client-side embedding — Configure an embedder once, then just send text queries. Meilisearch handles vectorization for both documents and searches
- Full-text search with typo tolerance, prefix matching, and language-aware tokenization
- Hybrid search combining keyword relevancy and semantic similarity in a single query — no need to orchestrate two search systems
- Faceted search with value distributions for building filter UIs
- Highlighting of matching terms in results
- Synonyms and stop words support
- Built-in ranking rules that combine text relevancy, semantic similarity, and custom sort attributes
Settings and parameters comparison
The below tables compare Qdrant concepts with their Meilisearch equivalents.Core concepts
| Qdrant | Meilisearch | Notes |
|---|---|---|
| Collection | Index | — |
| Point | Document | A Point is vector-first (vector + metadata payload). A Document is content-first (fields + optional vectors) |
| Payload | Document fields | Payload fields become top-level document fields |
| Vector | _vectors field or auto-generated via embedders | Meilisearch can auto-generate vectors from document content, so importing vectors is optional |
| Point ID (uuid or integer) | Document id (string) | Must convert to string |
| Named vectors | Multiple embedders | One embedder per vector name |
| Collection aliases | Index swap | Atomic swap of two indexes |
Indexing and storage
| Qdrant | Meilisearch | Notes |
|---|---|---|
payload_schema / payload index | filterableAttributes | Required for filtering |
| HNSW index config | Automatic (DiskANN-based) | No manual tuning needed |
| Quantization (scalar, product, binary) | Built-in binary quantization via embedders | Configured per embedder |
on_disk storage | Automatic | Meilisearch uses memory-mapped storage |
| Sharding / replication | Automatic (Meilisearch Cloud) | — |
| Snapshots | Dumps / Snapshots | — |
Search parameters
| Qdrant | Meilisearch | Notes |
|---|---|---|
query (precomputed vector) | q + hybrid with auto-embedder | Meilisearch embeds the query for you — no client-side vector computation |
| No built-in full-text search | q search param | Full-text search with typo tolerance, works standalone or combined with hybrid |
| No equivalent | hybrid.semanticRatio | Tune the balance between keyword and semantic results (0.0–1.0) |
filter.must | filter with AND | — |
filter.should | filter with OR | — |
filter.must_not | filter with NOT | — |
filter.match (exact value) | filter with = operator | — |
filter.range (gt, gte, lt, lte) | filter with >, >=, <, <= or TO | — |
filter.geo_bounding_box | _geoBoundingBox([lat, lng], [lat, lng]) in filter | — |
filter.geo_radius | _geoRadius(lat, lng, radius) in filter | — |
with_payload | attributesToRetrieve | Search param |
score_threshold | rankingScoreThreshold | Search param |
limit | limit | Search param |
offset | offset | Search param |
with_vectors | retrieveVectors | Search param |
| No equivalent | attributesToHighlight | Highlight matching terms in results |
| No equivalent | facets | Get value distributions for fields |
| No equivalent | sort | Sort by attributes (requires sortableAttributes) |
| No equivalent | attributesToCrop | Excerpt matching content |
Query comparison
This section shows how common Qdrant queries translate to Meilisearch. All Meilisearch examples below assume you have configured an auto-embedder — you simply send a text query and Meilisearch handles embedding automatically. No need to compute vectors client-side.Semantic search
With Qdrant, you must compute the query vector yourself before searching. With Meilisearch, you just send a natural language query: Qdrant:q text for you. Setting semanticRatio to 1.0 performs pure semantic search, just like Qdrant — but without managing vectors in your application code.
Hybrid search (keyword + semantic)
This is Meilisearch’s biggest advantage over Qdrant. A single query combines typo-tolerant keyword matching with semantic similarity — something that would require two separate systems with Qdrant: Meilisearch:semanticRatio of 0.5 gives equal weight to keyword and semantic results. Adjust this value to tune the balance: closer to 0.0 favors keyword matching, closer to 1.0 favors semantic similarity.
Filtered search
Qdrant:Attributes used in
filter must first be added to filterableAttributes.Geo search
Qdrant:The
_geo attribute must be added to both filterableAttributes and sortableAttributes.Faceted search
Qdrant has no equivalent for faceted search. In Meilisearch, you can retrieve value distributions for any filterable attribute: Meilisearch:Full-text search (no vectors)
Meilisearch also works as a standalone keyword search engine. If you don’t need semantic search for a particular query, omit thehybrid parameter entirely:
Meilisearch:
API methods
This section compares Qdrant and Meilisearch API operations.| Operation | Qdrant | Meilisearch |
|---|---|---|
| Create collection/index | PUT /collections/{name} | POST /indexes |
| Delete collection/index | DELETE /collections/{name} | DELETE /indexes/{index_uid} |
| Get collection/index info | GET /collections/{name} | GET /indexes/{index_uid} |
| List collections/indexes | GET /collections | GET /indexes |
| Upsert points/documents | PUT /collections/{name}/points | POST /indexes/{index_uid}/documents |
| Get point/document | GET /collections/{name}/points/{id} | GET /indexes/{index_uid}/documents/{id} |
| Delete points/documents | POST /collections/{name}/points/delete | POST /indexes/{index_uid}/documents/delete |
| Scroll/browse | POST /collections/{name}/points/scroll | GET /indexes/{index_uid}/documents |
| Search | POST /collections/{name}/points/search | POST /indexes/{index_uid}/search |
| Multi-search | POST /collections/{name}/points/search/batch | POST /multi-search |
| Create payload index | PUT /collections/{name}/index | PATCH /indexes/{index_uid}/settings |
| Get collection config | GET /collections/{name} | GET /indexes/{index_uid}/settings |
| Create snapshot | POST /collections/{name}/snapshots | POST /snapshots |
| Health check | GET /healthz | GET /health |