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

Meili Search Bundle Laravel Package

guikingone/meili-search-bundle

A Symfony bundle that integrates Meilisearch into your application, offering configuration, index management, and search services. It helps index Doctrine entities and query results through a simple, framework-friendly API, simplifying search setup and index synchronization.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the reality check — still true, with a caveat. guikingone/meili-search-bundle remains a Symfony bundle, not a Laravel package. It ships no service provider, no config/scout.php driver, no facade. The 0.1.0 release doesn't change that: the roadmap calls out Api-Platform support in progress, which is more Symfony-ecosystem surface area, not less. You cannot composer require it into a Laravel app and expect anything to bind. If you're on Laravel, the day-to-day answer is unchanged:

composer require meilisearch/meilisearch-php   # direct SDK
# or, more idiomatic:
composer require laravel/scout meilisearch/meilisearch-php

Set SCOUT_DRIVER=meilisearch and MEILISEARCH_HOST in .env, add the Laravel\Scout\Searchable trait to models, then php artisan scout:import "App\Models\Post".

What 0.1.0 changes about the project's status. The release is tagged Initial release, which means the package has been rebooted onto a fresh version line rather than continued from the old one. Treat everything you read about it — including older write-ups and the previous version-pinning advice — as superseded and re-verify against the actual tag. Two consequences that matter immediately:

  • The project is alive again (there's an active roadmap), but it is at 0.1.x, so the public API is not stable. composer require guikingone/meili-search-bundle:^0.1 and expect breaking changes in 0.2, 0.3, and so on — that's what a 0.x line licenses.
  • The surface is minimal. An initial release is not a mature one. Whatever you find in Command/, Index/, and Search/ is likely the whole of it; don't assume features from blog posts exist.

The bundle is worth reading in three situations: (a) you inherited a Symfony app that already uses it, (b) you're on API Platform and want to track the roadmap item, or (c) you want a reference for how to structure index configuration, document serialization, and event-driven sync — patterns you'll reimplement in Laravel.

Minimal Symfony setup (if you're actually in Symfony):

  1. composer require guikingone/meili-search-bundle:^0.1
  2. Register it in config/bundles.php (Symfony Flex may do this automatically).
  3. Create config/packages/meili_search.yaml — the config root is typically meili_search and covers hosts, api_key, and an indexes map of class → primary key. Verify the exact tree by opening DependencyInjection/Configuration.php in the vendor directory; at 0.1.0 the schema is whatever that file says, and the README may lag it.
  4. Run bin/console list meili to see the actual command names available in your installed version (create index, import, clear, search, etc.). On an initial release, this list is short and authoritative.
  5. Make one entity implement the bundle's searchable interface (something like getUid() + toArray()), index it, and query from a controller.

Where to look first in the source:

Directory What it tells you
DependencyInjection/Configuration.php The real config schema — trust this over the README, especially at 0.1.0
composer.json Which meilisearch/meilisearch-php constraint you're actually getting — this decides server compatibility
Command/ Every CLI entry point and its flags
Index/ Index creation, primary key handling, document saving
Search/ Query building, pagination, filters
EventSubscriber/ or EventListener/ How Doctrine entities get auto-synced

First use case to prove it works: index a single Post entity, curl http://localhost:7700/indexes to confirm the index exists, then curl a search query. If the curl works but the bundle doesn't, the problem is in the bundle's wiring, not Meilisearch.

Implementation Patterns

The architecture is three layers: client (Meilisearch HTTP), index (write path), search (read path). Every pattern below maps cleanly onto Laravel equivalents.

1. Configuration-as-classes (the bundle's best idea). The bundle lets you describe an index (primary key, searchable attributes, filterable attributes, ranking rules) in a dedicated class rather than scattering it in YAML. Do the same in Laravel with a small value object per index:

final class PostIndexSettings
{
    public static function apply(Client $client): void
    {
        $client->index('posts')->updateSettings([
            'searchableAttributes' => ['title', 'body'],
            'filterableAttributes' => ['status', 'author_id', 'published_at'],
            'sortableAttributes'   => ['published_at'],
        ]);
    }
}

Call it from a deploy step or an artisan command — not on every request. At 0.1.x this layer is the one most likely to change shape; keep your settings definitions in your own code, not in bundle-specific classes, so you can swap the driver underneath them.

2. Event-driven sync, with a queue. The bundle hooks Doctrine lifecycle events so entities are pushed to Meilisearch on persist/update/delete. In Laravel the analogue is model observers or Scout's built-in Searchable hooks:

class PostObserver
{
    public function saved(Post $post): void
    {
        dispatch(new SyncPostToSearch($post))->onQueue('search');
    }

    public function deleted(Post $post): void
    {
        dispatch(new RemovePostFromSearch($post->id))->onQueue('search');
    }
}

Keep it queued. Meilisearch writes are asynchronous tasks anyway — doing them inline in the request just doubles the latency for no benefit.

3. Explicit document serialization. The bundle's toArray() contract forces you to decide what actually lands in the index. Copy that discipline into Scout's toSearchableArray(): flatten relations, drop nulls, format dates as ISO-8601 or Unix timestamps consistently, and never include the full model.

public function toSearchableArray(): array
{
    return [
        'id'         => $this->id,
        'title'      => $this->title,
        'body'       => strip_tags($this->body),
        'author_id'  => $this->author_id,
        'status'     => $this->status,
        'published_at' => $this->published_at?->timestamp,
    ];
}

4. Bulk import over per-record writes. The bundle exposes import commands; use them. php artisan scout:import "App\Models\Post" is orders of magnitude faster than saving 50k models one at a time. Reserve per-record sync for user-generated edits after the initial backfill.

5. Two keys, two clients. Admin/master key for indexing and settings (server-side only), a scoped search-only key for anything the browser touches. The bundle's config has a single api_key, which is a footgun — don't reproduce that in Laravel. Use MEILISEARCH_KEY (admin) in the backend and generate tenant/search keys via the SDK for the frontend.

6. Health check and settings as code. Wrap a $client->health() call in a /health route or a scheduled command, and keep index settings in version-controlled classes so a fresh environment can be rebuilt with: create index → apply settings → import → verify.

7. Wrap the dependency, don't spread it. Because 0.1.0 is a new line with an actively moving roadmap (API Platform support in flight), the correct integration posture is a thin adapter: your own interface for "index this document", "remove this document", "apply settings", with the bundle behind it. When 0.2 reshuffles namespaces or the API Platform work changes the entry points, you edit one class rather than every call site.

Gotchas and Tips

0.x instability is now the headline gotcha. 0.1.0 is an initial release on a fresh line, with a roadmap that explicitly says API Platform support is in progress. That combination means: the public API will move, the config schema may move with it, and documentation will lag the code. Pin to ^0.1 in composer.json, commit the lockfile, and treat every minor bump as a breaking-change review rather than a routine update.

Version compatibility is a two-sided problem — check it first. MeiliSearch renamed to Meilisearch in 2021, and the HTTP API changed substantially across the v0.x → v1.x line (route restructuring, the task/update-id model, waitForTask semantics, settings endpoint changes). A rebooted 0.1.0 could pin anything. Check composer.json in the vendor directory before doing anything else and pin your Meilisearch server to a version that matches the bundled SDK. Mixing a 2024 Meilisearch container with an SDK from an older constraint will produce 404s on every call and waste a day.

Writes are asynchronous — search results lag. Meilisearch queues every write as a task. Right after save(), a search may not find the document. Production code must either waitForTask() (blocking, use sparingly) or accept eventual consistency and design UX around it. Never write a test that saves and immediately searches without waiting.

Bulk operations bypass Doctrine events. DQL UPDATE, DB::table()->update(), raw SQL imports, and queue workers running raw queries will not fire the bundle's subscribers (or Laravel observers). Your index drifts silently. Rule: any write path that isn't a model save must have explicit reindexing attached, or run a nightly scout:import reconciliation.

Primary key mismatches. Meilisearch infers the primary key on first insert. If your first document lacks the field, the index is poisoned and every subsequent insert fails. Always declare it explicitly (id) at index creation time, and make sure toSearchableArray() always includes it — even for soft-deleted or partial records.

Nulls and empty arrays in documents. Meilisearch rejects documents with inconsistent field types across records, and null in a filterable attribute is a common source of "invalid document" task failures. Normalize: use '' or omit the field rather than null.

Fresh-install traps are likelier on an initial release. A 0.1.0 that nobody has run in anger yet will have rough edges in exactly the places you least want them: container aliases, default config values, and the order in which services get wired. Budget for reading source rather than docs, and check bin/console debug:container | grep -i meili early to confirm the services you expect actually exist and under what aliases.

Config cache in Symfony. After editing meili_search.yaml, run bin/console cache:clear — otherwise the old compiled container is used and your new index definitions are invisible. In Laravel the equivalent trap is php artisan config:clear after touching config/scout.php.

Debugging is a curl exercise, not a PHP exercise. When the bundle/SDK reports an error, drop to HTTP directly:

curl -s http://localhost:7700/indexes | jq
curl -s http://localhost:7700/tasks?status=failed | jq '.results[].error'

The /tasks endpoint is where Meilisearch actually tells you what went wrong — the PHP layer frequently swallows it behind a generic exception. Task failures from bad settings or schema mismatches are the #1 cause of "indexing silently does nothing."

Extension points if you must stay on Symfony. Decorate or override the bundle's client service alias to swap in a custom HTTP client (timeouts, retries, logging) — find the alias via bin/console debug:container | grep -i meili. The search service is typically decoratable too, which is how you'd add multi-tenant filtering or query logging the bundle doesn't provide. On a 0.x release, prefer decoration over forking: you keep the upgrade path open when the next version lands.

Migration advice. For anything greenfield on Laravel: use Scout with the Meilisearch driver — it's maintained, tested against current Meilisearch, and gives you searchableAs(), shouldBeSearchable(), and engine-level settings out of the box. Borrow this bundle's concepts (index config classes, explicit document contracts, event-driven sync), not its code. If you're on Symfony or API Platform, the 0.1.0 reboot makes it defensible to evaluate again — but evaluate behind an adapter, with the lockfile pinned, and with the expectation that the API Platform work will churn the surface. If you're maintaining an existing Symfony app that already depends on it, isolate Meilisearch behind your own thin service interface so a future swap to the official SDK is a single-class change rather than a bundle-wide rewrite.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager