meilisearch/meilisearch-php
Official PHP client for Meilisearch, the open‑source search engine. Connect to Meilisearch or Meilisearch Cloud to index documents, configure indexes, and run fast, typo‑tolerant searches. Supports customizable HTTP clients and common PHP tooling.
## Getting Started
### Minimal Setup in Laravel
1. **Install the package** with Guzzle (recommended):
```bash
composer require meilisearch/meilisearch-php guzzlehttp/guzzle http-interop/http-factory-guzzle:^1.0
AppServiceProvider):
public function register()
{
$this->app->singleton(Meilisearch\Client::class, function ($app) {
return new Meilisearch\Client(
env('MEILISEARCH_HOST', 'http://127.0.0.1:7700'),
env('MEILISEARCH_MASTER_KEY', 'masterKey')
);
});
}
$client = app(Meilisearch\Client::class);
$index = $client->index('products');
$index->addDocuments([['id' => 1, 'name' => 'Laptop', 'price' => 999]]);
Pattern: Use in Laravel controllers for search-as-you-type:
public function search(Request $request)
{
$query = $request->input('q');
$index = app(Meilisearch\Client::class)->index('products');
$results = $index->search($query, [
'limit' => 10,
'attributesToRetrieve' => ['id', 'name', 'price'],
'attributesToHighlight' => ['name']
]);
return response()->json($results->getHits());
}
Pattern: Sync database changes to Meilisearch via model observers:
// app/Observers/ProductObserver.php
public function saved(Product $product)
{
$index = app(Meilisearch\Client::class)->index('products');
$index->updateDocument($product->id, $product->toArray());
}
Pattern: Enable faceted navigation in Laravel views:
$index = app(Meilisearch\Client::class)->index('products');
$results = $index->search($query, [
'facets' => ['categories', 'price_range']
]);
// Pass $results->getFacets() to Blade
Pattern: Handle long-running tasks with Laravel queues:
// Dispatch a job for bulk indexing
IndexProductsJob::dispatch($products);
// In the job
public function handle()
{
$index = app(Meilisearch\Client::class)->index('products');
$index->addDocuments($this->products);
}
Laravel Service Container: Bind the client to the container with custom configurations:
$this->app->bind(Meilisearch\Client::class, function ($app) {
$client = new Meilisearch\Client(
env('MEILISEARCH_HOST'),
env('MEILISEARCH_MASTER_KEY'),
new GuzzleHttp\Client(['timeout' => 5])
);
return $client;
});
API Key Management: Use the typed query objects (v2+) for security:
$key = $client->createKey(new CreateKeyQuery(
actions: [KeyAction::Search],
indexes: ['products'],
description: 'Public search key'
));
Search Middleware: Create middleware to inject search results:
public function handle($request, Closure $next)
{
$request->merge([
'search_results' => $this->search($request->query('q'))
]);
return $next($request);
}
Testing:
Use Laravel's Meilisearch facade in tests:
public function testSearch()
{
$client = $this->app->make(Meilisearch\Client::class);
$index = $client->index('test_products');
$index->addDocuments([['id' => 1, 'name' => 'Test Product']]);
$results = $index->search('test');
$this->assertCount(1, $results->getHits());
}
Async Operations:
addDocuments() returns immediately but may take time to process.$taskUid = $index->addDocuments($docs)->getTaskUid();
$task = $client->waitForTask($taskUid);
Index Settings:
filterableAttributes or searchableAttributes triggers a full index rebuild.$client->getTask($taskUid).Highlighting:
attributesToHighlight must be defined in searchableAttributes.$index->updateSearchableAttributes(['name', 'description']);
Pagination:
offset/limit can be slow for large datasets.after parameter (Meilisearch v1.30+).Typo Tolerance:
typoTolerance setting:
$index->updateTypoTolerance('lenient');
Enable Debug Mode:
$client = new Meilisearch\Client(
'http://localhost:7700',
'masterKey',
new GuzzleHttp\Client(['debug' => true])
);
Check Tasks: List pending tasks to debug slow operations:
$tasks = $client->getAllTasks();
foreach ($tasks as $task) {
if ($task->status === 'processing') {
// Handle stuck tasks
}
}
Validate Documents:
Ensure documents have unique id fields to avoid duplicates:
$documents = collect($products)->map(function ($product) {
return ['id' => $product->id, 'name' => $product->name];
})->toArray();
Custom HTTP Client: Extend the client for retry logic or logging:
$httpClient = new GuzzleHttp\Client([
'timeout' => 10,
'on_stats' => function (TransferStats $stats) {
Log::debug('Meilisearch request', [
'duration' => $stats->getHandlerStats()->getDuration(),
'size' => $stats->getHandlerStats()->getTransferStats()->getSize()
]);
}
]);
$client = new Meilisearch\Client('http://localhost:7700', 'masterKey', $httpClient);
Search Result Transformers: Create a transformer for consistent API responses:
$results = $index->search($query)->getHits();
$transformed = collect($results)->map(function ($hit) {
return [
'id' => $hit['id'],
'title' => $hit['title'],
'url' => route('products.show', $hit['id']),
'highlighted_title' => $hit['_formatted']['title'] ?? null
];
});
Event Listeners: Listen for Meilisearch task completion:
$client->onTaskCompleted(function ($task) {
Log::info("Task {$task->uid} completed with status: {$task->status}");
});
Laravel Scout Alternative: Use the package as a drop-in replacement for Scout:
// config/scout.php
'driver' => 'meilisearch',
// app/Providers/ScoutServiceProvider.php
public function boot()
{
Scout::extend('meilisearch', function ($app) {
return new MeilisearchEngine(
$app->make(Meilisearch\Client::class)
);
});
}
Environment Variables:
Use Laravel's .env for sensitive data:
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_MASTER_KEY=your_master_key
MEILISEARCH_INDEX=products
Index Creation:
addDocuments().$index = $client->index('products');
$index->updateSettings([
How can I help you explore Laravel packages today?