Library / SDK
meilisearch/meilisearch-python avatar
meilisearch/meilisearch-python

meilisearch-python: the Python client for the Meilisearch API

Python client for Meilisearch

606 stars122 forksPythonMIT

At a glance

What is it?
meilisearch-python is the official Python client for Meilisearch, a thin and typed layer over the REST API that covers indexing, typo tolerant search, filters, highlighting, hybrid search and custom document serialisation. Indexing is asynchronous and returns a task uid, the package needs Python 3.10 or newer, and the repository ships a docker-compose file so you can run a server and the client together.
Who is it for?
meilisearch-python suits Python teams that have chosen Meilisearch and want an idiomatic client, especially those who want search results in snake_case Python objects rather than camelCase JSON, or who need hybrid keyword and semantic search without hand-rolling the request body.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A thin client, and a snake_case answer for a camelCase API

meilisearch-python is the Meilisearch API client for Python developers, published on PyPI as meilisearch and MIT licensed. The README describes it in one line as the Meilisearch API client written for Python, and that is accurate.

Installation needs Python 3.10 or newer:

bash
pip3 install meilisearch

The dependency list is short and tells you what the client does. It depends on requests for the HTTP layer and on camel-converter with the pydantic extra. The second dependency is the interesting one, because Meilisearch is a JavaScript and Rust engine with camelCase field names, and the client converts those responses into snake_case Python attributes. A response field such as processingTimeMs arrives as processing_time_ms, and that is the difference between a client you use and a client you wrap.

The package is typed, with py.typed in the package data, so a type checker will use the client's own annotations rather than falling back to Any. The version is dynamic, read from meilisearch.version.__version__ at build time, which is how the client and the published artefact stay in step.

The documentation lives at meilisearch.github.io/meilisearch-python, and the README points at both the client documentation and the server's own API reference, which is the right division: the client is a thin layer, so the interesting documentation is about the API it calls.

For a user, the choice between Meilisearch Cloud and self-hosting the engine is separate from this client, and the README links both routes.

Indexing returns a task uid, not a finished write

The first thing to understand about using this client is that adding documents is asynchronous, and the client gives you the handle to follow.

The shape of the code is small:

python
import meilisearch

client = meilisearch.Client("http://127.0.0.1:7700", "masterKey")

index = client.index("movies")

An index is where the documents are stored, and index.add_documents(documents) returns a dict with a uid, in the example a uid of 0. The README also notes that if the index does not exist, Meilisearch creates it when you first add the documents, so there is no separate create step to forget.

With that uid you check the status of the addition using the task endpoint, and the statuses are enqueued, canceled, processing, succeeded or failed.

This is the part of the API that changes how you write application code. A document write is not complete when the call returns, it is complete when the task succeeds, and an application that ignores the task and immediately searches will find nothing and conclude the client is broken. The polling pattern is not optional.

The same task mechanism appears again later, and there it is not just informational. Updating filterableAttributes causes Meilisearch to rebuild the index, which the README says might take time depending on the size of the dataset, and again you track it with the task. So the client gives you one consistent way to know what the engine is doing, and the two operations where it matters most are indexing and a filterable attribute change.

Typo tolerance is the default, and the response is a plain dict

The search side of the client is where Meilisearch's own behaviour shows through, and the README's examples make it concrete.

The simplest search is a one-liner, and the comment above it says it all: Meilisearch is typo-tolerant.

python
index.search("caorl")

The response is a plain dictionary with hits, offset, limit, processingTimeMs, and the query that was actually run. The client is not wrapping the response in a rich object model, which is the consistent choice given that it already converts the field names.

Search options are passed as a second argument, the same way the search parameters are documented on the server side:

python
index.search(
    "phil",
    {
        "attributesToHighlight": ["*"],
    },
)

which returns a _formatted key inside each hit containing the highlighted fragments, in the example the title with the matched part wrapped in em tags.

Filters need one piece of setup first. To enable filtering you must add your attributes to the filterableAttributes index setting:

py
index.update_filterable_attributes(["id", "genres"])

and then a search can carry a filter expression:

py
index.search("wonder", {"filter": ["id > 1 AND genres = Action"]})

So the sequence for a filtered search is: declare the attributes, wait for the rebuild task, then filter. The client does not hide that ordering, and the README does not pretend the filter works immediately after the settings call.

Hybrid search blends keyword and semantic, and needs an embedder first

Hybrid search is the feature that pushes this client beyond a thin REST wrapper, and it comes with a precondition that is easy to miss.

The README states that you need to have an embedder configured in your index settings to use the feature. The call itself is one option:

python
index.search("action movie", {"hybrid": {"semanticRatio": 0.5, "embedder": "default"}})

Two parameters carry the meaning. The semanticRatio, between 0 and 1, controls the balance between keyword search and semantic search: 0 is only keyword search, 1 is only semantic search, and values in between are a mix of both approaches. The embedder parameter names which configured embedder to use for the semantic part.

The design is sensible for a search engine that wants to serve both exact-match queries, where a typo tolerant keyword matcher is right, and descriptive queries, where embeddings do better. The tradeoff is that a request now has two retrieval systems behind it, and the ratio is a guess about your traffic until you measure it.

The other thing to know is that this is a server capability. The client sends the hybrid options and the embedder name; the embedding, the vector index and the merging of the two result sets all happen in the engine. So the cost of hybrid search, both in latency and in the work of keeping an embedder configured, lands on the server side regardless of which client you use.

Documents with UUIDs and datetimes need your own encoder

The one place the client asks something of you is the document serialiser, and the README is clear that the standard JSON serialiser will not handle everything.

If your documents contain fields the Python JSON serialiser does not know how to handle, you can pass your own custom serialiser, and the example uses a JSONEncoder subclass that converts UUID and datetime objects to strings:

py
from datetime import datetime
from json import JSONEncoder
from uuid import uuid4


class CustomEncoder(JSONEncoder):
    def default(self, o):
        if isinstance(o, (UUID, datetime)):
            return str(o)

        # Let the base class default method raise the TypeError
        return super().default(o)


documents = [
    {"id": uuid4(), "title": "test 1", "when": datetime.now()},
    {"id": uuid4(), "title": "Test 2", "when": datetime.now()},
]
index.add_documents(documents, serializer=CustomEncoder)

The design point is that the fallback is a TypeError rather than a silent string conversion, so a field you forgot to handle fails loudly at the point of the write instead of being stored as a string you did not intend.

The comment in the code says you only need to perform this operation once, which refers to the filterable attribute update rather than the serialiser, and it is the same operation that triggers the index rebuild.

For most users this is the whole story of the client's extension surface. The rest of the API is passed through as keyword arguments, and this one hook is where you can change how your Python objects become JSON.

A compose file that runs the engine next to the client

The repository ships a docker-compose file, and it is the fastest way to see the client against a real server rather than a mock.

The meilisearch service runs the getmeili/meilisearch-enterprise image at version v1.55, publishes port 7700, and sets three environment variables: MEILI_MASTER_KEY is masterKey, MEILI_NO_ANALYTICS is true, and MEILI_EXPERIMENTAL_ALLOWED_IP_NETWORKS is any. The last of those is a development setting, and it is worth noticing that the file sets it deliberately rather than by accident.

The package service builds from docker/python.Dockerfile, mounts the repository into the working directory, and points MEILISEARCH_URL at http://meilisearch:7700, so the client in the container talks to the engine over the compose network by service name. A named volume for the virtualenv is declared, and the comment above it explains why: the host virtualenv is hidden and native wheels stay in the volume, which stops a host environment from leaking into the container.

That last point is a small piece of good practice that is easy to miss. A bind mount of the source tree plus a separate volume for the environment is the pattern that keeps a developer's local install from breaking the container.

The rest of the repository follows the same structure. There is a tests/ directory, a datasets/ directory, docs/ for the Sphinx documentation, tox.ini, a uv.lock, and a .code-samples.meilisearch.yaml file that presumably holds the code samples used in the documentation, which is how the snippets in the README stay in step with the client.

Tooling, typing and where the version numbers go

The project metadata shows a modern Python project with a real typing story.

The client ships type information and the dev groups are split by purpose. The test group is pytest with coverage and tox, the lint group is mypy and ruff, and the docs group is Sphinx with the read the docs theme. The tool configuration is uv, with a fork strategy of fewest and the dev, test and lint groups enabled by default.

Ruff is configured with a line length of 100 and a rule selection covering the usual ground, pycodestyle errors and warnings, pyflakes, bugbear, isort and pyupgrade. mypy is there for the type checking, and the package data includes py.typed so consumers get the annotations.

The release numbering tells you about the relationship with the server. The versions are v0.41.1, v0.42.0 and v0.43.0, published in June and July 2026, and the last push to the repository was on 2026-10-14. A client version below 1.0 with minor bumps every few weeks is a client that is tracking a server that is also moving, and the README keeps a section titled Compatibility with Meilisearch for exactly that reason.

Two things the README also makes explicit. The client is not the engine: Meilisearch itself is a separate open source search engine project, and the README links it for people who want to understand what is being called. And the project offers a hosted option alongside self-hosting, with the client working against either, since the difference is where the server runs rather than how the client speaks to it.

For a team, the practical cost of this client is low. It is a maintained, typed, well tested library with a small dependency footprint, and the work it saves is the camelCase to snake_case conversion and the task polling that every implementation would otherwise get wrong.

Editorial conclusion

meilisearch-python suits Python teams that have chosen Meilisearch and want an idiomatic client, especially those who want search results in snake_case Python objects rather than camelCase JSON, or who need hybrid keyword and semantic search without hand-rolling the request body. It does not suit a project that wants a search engine, because this is a client and the engine is a separate Rust server, and it does not suit you if your documents need a custom encoder and you would rather not maintain one, since the standard JSON serialiser will not handle UUIDs or datetimes. Check three things before you adopt it: that your Meilisearch server version is one this client version is compatible with, since the README keeps a compatibility section for that, that your filters are declared as filterable attributes before you use them, and that you handle the task uid rather than assuming a document write has already landed. The last push to the repository was on 2026-10-14 and the newest release is v0.43.0 from 2026-07-22.

Frequently asked questions

How do I install meilisearch-python?

Python 3.10 or newer is required, and the package installs with pip3 install meilisearch. The client depends on requests and on camel-converter with the pydantic extra, which is what converts the server's camelCase fields into snake_case Python attributes.

How do I know when my documents have been indexed?

Adding documents returns a task uid rather than a finished write, and you check that task for a status of enqueued, canceled, processing, succeeded or failed. The same mechanism tracks index rebuilds caused by updating filterableAttributes.

How do I use filters with meilisearch-python?

You must first add the attributes to the filterableAttributes index setting with index.update_filterable_attributes, wait for the rebuild task, and then pass a filter expression to the search. Meilisearch rebuilds the index when filterableAttributes changes, which can take a while on a large dataset.

What does hybrid search require in meilisearch-python?

You need an embedder configured in your index settings first. The request takes a hybrid option with a semanticRatio between 0 and 1, where 0 is keyword only and 1 is semantic only, and an embedder naming which configured embedder to use.

Can I store UUIDs and datetimes as document fields?

Not with the default JSON serialiser, but you can pass your own. The README shows a JSONEncoder subclass that converts UUID and datetime values to strings, passed to add_documents as serializer=CustomEncoder, with the base class raising a TypeError for anything unhandled.

Official sources

  1. License: MIT
  2. meilisearch/meilisearch-python on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/meilisearch-meilisearch-python.svg)](https://hysenlabs.com/projects/meilisearch-meilisearch-python)

Was this useful?