Library / SDK
meilisearch/meilisearch-go avatar
meilisearch/meilisearch-go

meilisearch-go: the Go client, with task-aware indexing and per-resource models

Golang wrapper for the Meilisearch API

680 stars124 forksGoMIT

At a glance

What is it?
meilisearch-go is Meilisearch's Go API client, generated against the search engine's REST surface and organised into one Go file per resource area, from index settings to webhooks and chat. Its most useful design choice for application code is that writes return a task you can poll, so an indexing operation is something you wait for rather than something you hope finished.
Who is it for?
Adopt meilisearch-go if you are building a Go service on top of Meilisearch and want the client to track the API surface without hand-written HTTP calls, especially if you need the task polling that indexing requires or the mocks for testing. Do not adopt it if you are on Go below 1.21, since the module declares that version as its floor, and check the compatibility section of the README before assuming a client version matches your server.
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 4 days ago.
What is it written in?
Mainly Go, 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 generated client, split into one file per API area

meilisearch-go is described as the Meilisearch API client for Go developers, and the repository layout shows how it is built.

The top level is a flat list of Go files that map one to one onto the API's resource areas: index.go for index operations, index_document.go, index_search.go, index_settings.go and index_task.go, then models_chat.go, models_documents.go, models_indexes.go, models_keys.go, models_networks.go, models_search.go, models_settings.go, models_tasks.go and models_webhooks.go. Alongside them sit client.go, options.go, error.go, encoding.go, hit.go, field.go, features.go, helper.go, enum.go, chat.go and the matching interface files such as index_interface.go and meilisearch_interface.go.

That last group matters. Every resource has an interface file next to its implementation file, which is the pattern that makes the mocks possible. A .mockery.yml file at the root drives the generation of the mocks/ directory, so the test doubles are produced from the same interfaces the client implements rather than being maintained by hand.

The module itself declares go 1.21 and the README titles its installation section for 1.21 or newer, so the Go toolchain floor is explicit. The dependency list in go.mod is short: brotli for compression, golang-jwt for token handling, and testify for tests, plus a YAML library and objx as indirect dependencies. A search client that pulls in a large dependency tree is harder to adopt in an existing build, and this one does not.

The other structural detail is the separation of the SDK into a core library and generated code. The examples/ directory contains runnable programs for add_documents, chat_stream, create_index_settings, facet_search, manage_tasks, multi_search and search, and there is a separate integration/ directory, so the test suite is split between unit tests and tests that talk to a real server.

Writing documents returns a task, and the task is the point

The first example in the README is adding documents, and the detail that matters is the return value. The call returns a task and an error, and the task carries a TaskUID:

go
package main

import (
	"fmt"
	"os"

	"github.com/meilisearch/meilisearch-go"
)

func main() {
	client := meilisearch.New("http://localhost:7700", meilisearch.WithAPIKey("foobar"))

	// An index is where the documents are stored.
	index := client.Index("movies")

	// If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.
	documents := []map[string]interface{}{
        { "id": 1, "title": "Carol", "genres": []string{"Romance", "Drama"} },
        { "id": 2, "title": "Wonder Woman", "genres": []string{"Action", "Adventure"} },
        { "id": 3, "title": "Life of Pi", "genres": []string{"Adventure", "Drama"} },
        { "id": 4, "title": "Mad Max: Fury Road", "genres": []string{"Adventure", "Science Fiction"} },
        { "id": 5, "title": "Moana", "genres": []string{"Fantasy", "Action"} },
        { "id": 6, "title": "Philadelphia", "genres": []string{"Drama"} },
	}
	task, err := index.AddDocuments(documents, nil)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	fmt.Println(task.TaskUID)
}

The comment about the index is worth pausing on, because it describes Meilisearch's model rather than the client's. If the index does not exist, Meilisearch creates it when you first add the documents. You do not have to create it, and you do not have to check first.

The task is the other half. The README explains that with the taskUID you can check the status of the documents addition using the task endpoint, and that the status is one of enqueued, canceled, processing, succeeded or failed. So indexing in Meilisearch is asynchronous by design, and the client surfaces that fact rather than hiding it. The immediate practical consequence is that a search issued straight after an add can legitimately miss the new document, and code that treats the add as complete the moment the HTTP call returns is relying on a race.

The type of the payload is map[string]interface{}, so the documents are untyped maps rather than Go structs. That keeps the example short and matches the engine's own JSON document model, at the cost of the compiler checking your field names.

Searching returns hits, offset, limit and a processing time

The search side is where most applications spend their time, and the README shows the response shape as well as the call.

The call takes a query string and an optional request struct, and the request struct is where the search parameters live:

go
package main

import (
    "fmt"
    "os"

    "github.com/meilisearch/meilisearch-go"
)

func main() {
    // Meilisearch is typo-tolerant:
    searchRes, err := client.Index("movies").Search("philoudelphia",
        &meilisearch.SearchRequest{
            Limit: 10,
        })
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println(searchRes.Hits)
}

The comment about typo tolerance is the point of that example. The query is misspelled, and the response still returns the right document:

json
{
  "hits": [{
    "id": 6,
    "title": "Philadelphia",
    "genres": ["Drama"]
  }],
  "offset": 0,
  "limit": 10,
  "processingTimeMs": 1,
  "query": "philoudelphia"
}

Two details in that response are worth noticing. The query field echoes back the misspelling, so the response tells you what was actually searched rather than what you meant, which is useful when you are debugging a relevance problem. And processingTimeMs is reported per query, so you can see the engine's own view of how long the search took.

The richer options are set on the same SearchRequest struct. The custom search example sets AttributesToHighlight to a slice containing a single asterisk, and the response then carries a _formatted object with the matched term wrapped in em tags. The filterable attributes example is a separate kind of operation altogether, and the README is clear that it is a one-time index setting change rather than something you do per query.

Filters need an index setting first, and rebuilding has a cost

Filtering is the one feature in the README where the client cannot do the work for you, and the order of operations matters.

The README states it directly: if you want to enable filtering, you must add your attributes to the filterableAttributes index setting, and you only need to perform this operation once. The call is a single line:

go
task, err := index.UpdateFilterableAttributes(&[]string{"id", "genres"})

The caveat that follows is the important one. Meilisearch will rebuild your index whenever you update filterableAttributes, and depending on the size of your dataset that might take time, and the task can be tracked.

That is a genuine operational consideration rather than a footnote. A filterable attribute change is a schema change to the index, and on a large index it is a reindex, not a settings update. If you do it as part of a deployment that also expects search to be immediately available, you have a window where the index is being rebuilt.

Note also the return type: another task, with another UID. Every write in this API, whether it is adding documents or changing index settings, is a task. The client consistently gives you a handle on the asynchronous operation rather than a bare success flag, which is the right shape for the engine and makes the client's behaviour predictable.

The examples directory reinforces the same structure with dedicated programs for facet_search, create_index_settings and manage_tasks. A project that treats the client as a set of independent operations, rather than one monolithic call, is what the library's own layout suggests.

Options, mocks and the rest of the client surface

Beyond search and indexing, the repository documents a few more capabilities, and the table of contents lists them explicitly: a Customize Client section, a Make SDK Faster section, a Using mocks for testing section with an Available Mocks subsection, a Compatibility with Meilisearch section, and a Benchmark Performance section.

The client is constructed with an options pattern, which the first example shows with meilisearch.New taking a host URL and a WithAPIKey option. The options.go file and its test file, options_test.go, are where the full set of construction options live, and for a client that talks to a remote service the options are where you find the timeouts, the transport configuration and the host.

The mocks deserve a note. The .mockery.yml file and the mocks/ directory, together with the per-resource interface files, mean you can substitute a fake for the client in your own tests without a running Meilisearch instance. For an application whose test suite would otherwise need a container or a live server for every case, that is a real saving, and it is the reason the interfaces are separated from the implementations in the first place.

The compatibility section is the one to read before you pin a version. A client tracks an API, and the API moves, so the client version and the server version have a relationship that the README documents rather than leaving you to infer. The recent releases are v0.36.3 from 2026-06-01, v0.36.2 from 2026-04-13 and v0.36.1 from 2026-02-10, and the last push was on 2026-10-19, so the client is being maintained even though the newest published tag is a few months old.

There is also a Dockerfile and a docker-compose.yml for local work. The compose file starts a Meilisearch service alongside a package service, and the environment sets MEILISEARCH_URL, MEILI_MASTER_KEY and MEILI_NO_ANALYTICS. The image tag it uses is getmeili/meilisearch-enterprise:latest, and the Go side is built on golang:1.21-bookworm with golangci-lint copied in from a golangci-lint v2.3.0 image.

Editorial conclusion

Adopt meilisearch-go if you are building a Go service on top of Meilisearch and want the client to track the API surface without hand-written HTTP calls, especially if you need the task polling that indexing requires or the mocks for testing. Do not adopt it if you are on Go below 1.21, since the module declares that version as its floor, and check the compatibility section of the README before assuming a client version matches your server. Before you commit, run the client against the docker-compose setup in the repository, which starts a Meilisearch instance with a master key, and write your first indexing call to handle the task UID rather than assuming the documents are searchable the moment the request returns.

Frequently asked questions

How do I install the Meilisearch Go client?

Run go get github.com/meilisearch/meilisearch-go. The module declares Go 1.21, and the README's installation section is titled for 1.21 or newer, so that is the toolchain floor.

How does indexing work in meilisearch-go, and how do I know it finished?

Adding documents returns a task and an error, and the task carries a TaskUID. You can then check the status through the task endpoint, where the state is one of enqueued, canceled, processing, succeeded or failed, so indexing is asynchronous and the client surfaces the handle rather than hiding it.

Do I need to create an index before adding documents with meilisearch-go?

No. The README states that if the index does not exist, Meilisearch creates it when you first add the documents. The client exposes that as client.Index("movies") and index.AddDocuments(documents, nil).

How do I enable filtering in meilisearch-go?

You must first add the attributes to the filterableAttributes index setting, with index.UpdateFilterableAttributes, and you only need to do it once. Meilisearch rebuilds the index whenever that setting changes, and the returned task can be tracked.

Can I use meilisearch-go in tests without running a Meilisearch server?

Yes. The repository includes a .mockery.yml configuration, a mocks/ directory and per-resource interface files such as index_interface.go and meilisearch_interface.go, so the mocks are generated from the same interfaces the client implements. The README also documents a Using mocks for testing section with an Available Mocks list.

Official sources

  1. License: MIT
  2. meilisearch/meilisearch-go 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-go.svg)](https://hysenlabs.com/projects/meilisearch-meilisearch-go)

Was this useful?