Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Search Bundle Laravel Package

meilisearch/search-bundle

A Symfony bundle that integrates Meilisearch into your application, delivering fast, typo-tolerant search and autocomplete with simple configuration, automatic indexing, and customizable searchable entities.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the obvious fork in the road: this is a Symfony bundle, not a Laravel package. meilisearch/search-bundle depends on symfony/framework-bundle, symfony/doctrine-bridge and auto-registers a compiler pass — it will not boot inside a Laravel app. If you're shipping Laravel, the equivalent day-to-day stack is laravel/scout + the meilisearch driver (composer require laravel/scout meilisearch/meilisearch-php), and the mental model maps almost 1:1 (searchable model → toSearchableArray() → Meilisearch index). Read this bundle's docs anyway if you're working in a mixed PHP shop, because the concepts — index prefixing, serializer groups, Doctrine event hooks, explicit filterable attributes — transfer directly. If you genuinely have a Laravel app talking to a Symfony service, the integration boundary is the Meilisearch HTTP API, not this bundle.

Assuming a Symfony context:

composer require meilisearch/search-bundle

Flex wires the bundle. Configure it in config/packages/meili_search.yml:

meili_search:
    url: '%env(MEILISEARCH_URL)%'
    api_key: '%env(MEILISEARCH_API_KEY)%'
    prefix: '%env(MEILI_PREFIX)%'   # e.g. 'dev_', 'test_'
    batch_size: 500

Mark an entity and its identifier:

use MeiliSearch\Bundle\Attribute\Searchable;
use MeiliSearch\Bundle\Attribute\SearchableId;

#[Searchable]
class Article
{
    #[SearchableId]
    public function getId(): ?int { return $this->id; }

    public function getTitle(): string { return $this->title; }
}

Then hydrate the index once and search:

bin/console meilisearch:import
$result = $this->searchService->search(Article::class, 'meilisearch', ['limit' => 10]);
foreach ($result->getHits() as $hit) { /* ... */ }

Where to look first: config/packages/meili_search.yml (the indices block is where class → index mapping lives), MeiliSearch\Bundle\SearchService (search() / SearchResult::getHits()), the meilisearch:* console commands, and the meilisearch-php client it wraps. First real use case: make one read-heavy listing page (catalog, docs, people directory) searchable, with the Doctrine listener doing incremental sync, then a nightly meilisearch:import as a safety net.

Implementation Patterns

Let Doctrine do the sync. The bundle's selling point is the event listeners on postPersist / postUpdate / postRemove. Write your entity normally and the index follows. This is the "seamless" path and it's the right default for CRUD-shaped admin backends.

Control the payload with serializer groups. Don't let the bundle serialize your whole entity — relations explode, $password leaks, and blobs get shipped to Meilisearch. Either expose a narrow surface of getters or use enable_serializer_groups: true with explicit serializer_groups per index, and annotate only the fields you want indexed. Treat the search document as a read model, not your ORM entity.

Bulk import is the pattern for anything large. For fixtures, migrations, backfills, or a 500k-row table, run meilisearch:import with an increased batch_size rather than relying on per-entity listeners. meilisearch:clear before a full rebuild keeps the index consistent if a run fails halfway.

Pass query parameters, don't post-filter in PHP. search() accepts Meilisearch search params directly — filter, facets, sort, attributesToRetrieve, limit, offset. Pushing filtering into Meilisearch is the difference between 20ms and 2s.

Reach for the raw client past the 80% line. Settings (ranking rules, synonyms, stop words), filterable/sortable attribute declaration, task polling, multi-search — inject MeiliSearch\Client and talk to it directly. Don't fight the bundle's abstraction for configuration work it doesn't model.

Prefix per environment. prefix: '%env(MEILI_PREFIX)%' with dev_ / test_ / nothing-in-prod is the standard way to keep a shared Meilisearch instance from cross-contaminating environments. This one config line saves you from a very confusing afternoon.

Testing: in config/packages/test/, point at a throwaway index prefix, disable the Doctrine listeners (or set the prefix and meilisearch:clear in setUp), and waitForTask() after any write you intend to assert on — Meilisearch is async by default and your test will race the indexer.

Gotchas and Tips

  • Filters silently do nothing until attributes are declared filterable. Meilisearch only applies a filter on attributes listed in filterableAttributes. The bundle doesn't infer this from your entity. Symptom: your query returns everything, no exception thrown. Same for sort and sortableAttributes. Declare them explicitly on the client, once per index.
  • The Doctrine listener fires inside the transaction. Indexing on postPersist pushes the document before commit; a rollback leaves a phantom document in Meilisearch, and a relationship loaded lazily at that moment may be stale. For anything correctness-sensitive, index after commit (Symfony's kernel.terminate or Messenger) rather than trusting the listener.
  • N entities = N HTTP calls. Loading fixtures or a bulk import through the listener path is brutally slow. Set batch_size up, disable listeners during bulk work, and re-import.
  • The primary key must be a scalar and stable. Composite keys, UUID objects, or a key that changes on update will orphan documents. Meilisearch infers id; make sure your #[SearchableId] maps to something immutable.
  • Config key names have churned across versions. Older releases used master_key where current ones use api_key; the attribute API replaced the earlier @Searchable annotation / SearchableObjectInterface approach. Pin the version and check the CHANGELOG before copying snippets from a blog post — most search results online are for a two-year-old release.
  • Watch the API key tier. The key you index with needs write permissions; the key your frontend uses for instant search should be a scoped, search-only key. Don't ship the admin key to a browser just because the bundle's config makes it convenient.
  • Small ecosystem, official maintainer. 154 stars and ~0 dependents reflect that this is a thin, single-purpose split repo under the Meilisearch org — it's maintained, but the community surface is small, so expect to read the source (SearchService, the listener classes) rather than find Stack Overflow answers. Budget for that. Also set MEILI_NO_ANALYTICS=true in CI.
  • Extension points worth knowing: a custom SearchService decorator for logging/sanitizing queries, a custom index-name resolver if your naming convention differs, and Messenger for async reindexing. Those three cover most of what you'll want to bend.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin