meilisearch/meilisearch-laravel-scout
Official Meilisearch driver for Laravel Scout. Make your Eloquent models instantly searchable with fast, typo-tolerant full-text search, filtering, sorting, custom ranking rules, and simple index configuration—all through familiar Scout model methods.
⚠️ This package is archived and last released 2021-03-10 (v0.12.5). For a new project today, use the successor official package (
meilisearch/meilisearch-laravel-scoutwas superseded by the driver now bundled with Scout / maintained under the Meilisearch org). Only adopt this exact package if you're pinning an existing 2021-era stack.
Assuming you're working with it as-is, the minimal path is:
Install and pin.
composer require meilisearch/meilisearch-laravel-scout:^0.12
The only real change in v0.12.5 is that meilisearch/meilisearch-php moved from v0.16.0 → v0.17.0. Verify the transitive pin with composer why meilisearch/meilisearch-php — you should see ^0.17.
Wire the driver. Publish Scout config and set:
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
In config/scout.php confirm the meilisearch block points at those env vars.
Register the model.
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'title' => $this->title,
'excerpt' => $this->excerpt,
'status' => $this->status,
'created_at' => $this->created_at?->timestamp,
];
}
}
Index and query.
php artisan scout:import "App\Models\Post"
Post::search('laravel')->get();
Where to look first: the package's service provider (to confirm the Scout engine is bound to meilisearch), config/scout.php, and the meilisearch-php v0.17.0 changelog — the client bump is the only meaningful delta in this release and the likely source of any behavior change.
First use case: one model, one index, a search() call returning results. Don't try filters, facets, or multi-search until the client↔server handshake is proven.
toSearchableArray(). Never return $this->toArray(). Relations, hidden fields, and full timestamps bloat the index, complicate filtering, and trip document-size expectations. Keep the array flat and intentional.searchableAs() so staging and production never collide:
public function searchableAs(): string
{
return config('app.env') . '_posts';
}
This one override is the highest-leverage pattern in the whole driver.filterableAttributes, sortableAttributes, rankingRules, synonyms, and maxTotalHits should be applied by an artisan command or seeder, run idempotently on deploy — not clicked into the dashboard. This is also your fastest regression check after a dependency bump.SCOUT_QUEUE=true with a running worker. Synchronous indexing is fine for local dev and hazardous during bulk writes or imports.scout:flush → scout:import → verify count is a repeatable operation. Bake it into a post-deploy hook or migration for models whose searchable schema changes.Post::search('laravel')
->where('status', 'published')
->orderBy('created_at', 'desc')
->paginate(20);
where and orderBy only work on attributes declared in filterableAttributes / sortableAttributes. Declare up front.$client = app(\Meilisearch\Client::class);
$client->index('posts')->updateFilterableAttributes(['status', 'author_id']);
multiSearch() directly rather than looping search() calls; it saves round-trips and gives consistent ordering.meilisearch/meilisearch-php in composer.lock; avoid floating constraints.meilisearch-php v0.17 targets Meilisearch server ~0.21+. A newer client against an older server usually returns confusing 400s on settings updates rather than a clear version error. Pin your Meilisearch Docker image tag alongside the PHP client.toSearchableArray() changes are inert until reindexed. Adding a field does nothing for existing documents until scout:import runs for that model. This is the #1 "why is my search broken" cause.where on a non-filterable attribute returns empty or throws depending on the server version. Always push settings before querying; make the settings command idempotent so it can run on every deploy.shouldBeSearchable() or handling deleted/forceDeleted events yourself; otherwise soft-deleted rows linger in the index.maxTotalHits to 1000. Deep pagination silently truncates until you raise it in settings — a nasty surprise in admin UIs.scout:status. Inspect what actually landed via Post::search('')->raw() or GET /indexes/{uid}/documents. For task failures, GET /tasks/{uid} is your friend.searchableAs() for index isolation, and shouldBeSearchable() for conditional indexing.How can I help you explore Laravel packages today?