alejandroakbal/laravel-scout-advanced-meilisearch
Laravel Scout extension adding advanced query builder filters (comparisons, grouped where/orWhere, between, notIn) plus two compatible drivers: meilisearch_advanced for better Meilisearch filtering/total counts and collection_advanced for in-memory testing.
Installation
composer require alejandroakbal/laravel-scout-advanced-meilisearch
Publish the config file:
php artisan vendor:publish --provider="Alejandroakbal\ScoutAdvancedMeilisearch\ScoutAdvancedMeilisearchServiceProvider" --tag="config"
Configure .env
SCOUT_DRIVER=meilisearch-advanced
MEILISEARCH_ADVANCED_HOST=http://localhost:7700
MEILISEARCH_ADVANCED_API_KEY=your-api-key
Set Up Model
Add Searchable trait and configure toSearchableArray():
use Alejandroakbal\ScoutAdvancedMeilisearch\Searchable;
class Product extends Model
{
use Searchable;
public function toSearchableArray()
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'price' => $this->price,
'tags' => $this->tags->pluck('name'),
];
}
}
First Search
$results = Product::search('wireless headphones')->get();
Indexing
Product::query()->forceSync();
chunk() for large datasets:
Product::chunk(100, function ($products) {
$products->searchable();
});
Advanced Queries
where() with Meilisearch syntax:
Product::search('headphones')->where('price', '<', 100)->get();
'typo_tolerance' => 'strict',
$results = Product::search('headphones')->get(['facets' => ['tags']]);
Pagination
$results = Product::search('headphones')->paginate(10);
Custom Sorting
toSearchableArray() to include sort fields:
public function toSearchableArray()
{
return [
'title' => $this->title,
'_sortable_price' => $this->price, // Custom field for sorting
];
}
Product::search('headphones')->sortBy('_sortable_price', 'desc')->get();
Hybrid Search Combine with other Scout drivers (e.g., database fallback):
Product::search('headphones')->with(['fallback' => true])->get();
searchableSynced/searchableDeleted for analytics or logging.Route::middleware(['auth'])->group(function () {
Route::get('/search', [SearchController::class, 'index']);
});
return Product::search(request('q'))->get()->map(function ($product) {
return $product->load('reviews')->toArray();
});
Schema Mismatches
toSearchableArray() returns a consistent structure. Meilisearch throws errors on schema changes.meilisearch:reset-index artisan command to rebuild the index:
php artisan meilisearch:reset-index --model=Product
Rate Limiting
429 errors.Case Sensitivity
unicode tokenizer for case-insensitive searches:
'searchableAs' => 'unicode',
Nested Relationships
toSearchableArray(); flatten relationships to prevent performance issues.with() to eager-load relationships after searching:
$products = Product::search('headphones')->with('reviews')->get();
Partial Updates
$product->deleteFromSearch();
$product->searchable();
config/scout.php:
'log' => env('SCOUT_LOG', true),
http://localhost:7700.$query = Product::search('headphones')->toMeilisearchQuery();
Custom Analyzers
Override the default analyzer in config/scout-advanced-meilisearch.php:
'analyzer' => 'fr', // French analyzer
Webhooks Subscribe to Meilisearch events (e.g., index updates) via the Meilisearch HTTP API.
Multi-Tenancy Use separate Meilisearch indices per tenant:
public function getSearchableIndex()
{
return 'tenant_' . auth()->id() . '_products';
}
Async Indexing
Queue searchable() calls for background processing:
ProductCreated::dispatch($product); // Event dispatches SearchableJob
Register the job:
use Alejandroakbal\ScoutAdvancedMeilisearch\Jobs\SearchableJob;
SearchableJob::dispatch($product);
How can I help you explore Laravel packages today?