laravel/scout
Laravel Scout adds driver-based full-text search to Eloquent models, automatically syncing model changes to your search indexes. Supports Algolia, Meilisearch, and Typesense for fast, scalable searching in Laravel apps.
Installation:
composer require laravel/scout
For a specific driver (e.g., Algolia, Meilisearch, or Typesense), install its corresponding package:
composer require algolia/algoliasearch-client-php # Algolia
composer require meilisearch/meilisearch-php # Meilisearch
composer require typesense/typesense-php # Typesense
Configure Driver:
Add your driver configuration to .env (e.g., SCOUT_DRIVER=algolia, ALGOLIA_APP_ID=..., ALGOLIA_SECRET=...).
Default driver is now collection (in-memory), but switch to your preferred driver.
Make a Model Searchable:
Use the HasScoutSearchable trait in your Eloquent model:
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
// Model logic...
}
First Search:
$results = Post::search('laravel')->get();
Scout automatically syncs model changes to the search index.
// Search posts with a query
$posts = Post::search('backend development')->get();
// Get first result
$firstPost = Post::search('backend')->first();
// Paginate results
$posts = Post::search('laravel')->paginate(10);
Syncing Data: Scout automatically syncs model changes (create/update/delete) to the search index. For bulk operations, use:
php artisan scout:import "App\Models\Post"
Or queue the import for large datasets:
php artisan scout:queue "App\Models\Post"
Customizing Searchable Attributes:
Override toSearchableArray() to define which fields are searchable:
public function toSearchableArray()
{
return [
'title' => $this->title,
'body' => $this->body,
'tags' => $this->tags,
];
}
Filtering Results:
Use the where() method to filter search results:
$posts = Post::search('laravel')
->where('category', 'backend')
->where('published', true)
->get();
Advanced Queries: Use query callbacks for complex logic:
$posts = Post::search(function ($query) {
$query->where('title', 'laravel')
->where('category', 'backend')
->orderByDesc('created_at');
})->get();
Customizing Engines: Extend the default engine behavior by creating a custom engine:
use Laravel\Scout\Engines\Engine;
class CustomEngine extends Engine
{
public function update($model)
{
// Custom logic
}
public function delete($model)
{
// Custom logic
}
public function search($query)
{
// Custom logic
}
}
Register it in config/scout.php:
'engine' => CustomEngine::class,
Queueing Search Jobs: For performance, queue searchable sync jobs:
Post::addGlobalScope(new SearchableByUserScope());
Configure queue settings in .env:
SCOUT_QUEUE_CHUNK=100
SCOUT_QUEUE_WORKER=redis
Soft Deletes:
Scout handles soft deletes automatically. Use forceDelete() to permanently remove from the index:
$post->forceDelete(); // Removes from search index
Driver-Specific Features:
setSettings() to customize index behavior.Testing:
Use the collection driver for testing:
config(['scout.driver' => 'collection']);
Mock search responses in tests:
$this->partialMock(Engine::class, function ($mock) {
$mock->shouldReceive('search')->andReturn([...]);
});
Race Conditions:
Avoid concurrent writes to the same model during imports. Use scout:queue for large datasets:
php artisan scout:queue "App\Models\Post"
Driver-Specific Quirks:
ALGOLIA_APP_ID and ALGOLIA_SECRET are correctly set. Numeric filters may require explicit casting.Pagination Issues:
Typesense may return limited records due to integer overflow. Use perPage() explicitly:
$posts = Post::search('query')->paginate(20)->perPage(20);
Soft Deletes:
Scout skips deleted models by default. Use forceDelete() to ensure removal from the index:
$post->delete(); // Soft delete (keeps in index)
$post->forceDelete(); // Hard delete (removes from index)
Unique Indexing: Prevent duplicate jobs during reindexing by enabling unique indexing:
php artisan scout:import "App\Models\Post" --unique
Log Search Queries:
Enable Scout logging in config/scout.php:
'log' => env('SCOUT_LOG', false),
Check Index Settings: Sync index settings manually:
php artisan scout:settings "App\Models\Post"
Verify Driver Connection: Test your driver connection:
php artisan scout:flush "App\Models\Post"
php artisan scout:import "App\Models\Post"
Handle Missing Collections: For Typesense, ensure collections exist before searching:
if (!$this->collectionExists($model)) {
$this->createCollection($model);
}
Custom Searchable Attributes: Dynamically compute searchable attributes:
public function toSearchableArray()
{
return [
'content' => $this->title . ' ' . $this->body,
'slug' => Str::slug($this->title),
];
}
Post-Search Callbacks: Modify results after search:
Post::search('query')->afterRawSearch(function ($results) {
return $results->map(fn ($item) => [
'id' => $item['id'],
'title' => $item['title'],
'score' => $item['_score'],
]);
})->get();
Custom Engines:
Extend Laravel\Scout\Engines\Engine for full control over search behavior. Example:
class CustomEngine extends Engine
{
public function search($query)
{
$results = $this->client->search($query->toArray());
return $results['hits'];
}
}
Driver-Specific Optimizations:
setSettings() to optimize for your use case.Default Driver:
The default driver is now collection (in-memory). Change it in .env:
SCOUT_DRIVER=algolia
Queue Configuration:
Configure queue settings in .env:
SCOUT_QUEUE_CHUNK=100
SCOUT_QUEUE_WORKER=redis
Environment-Specific Drivers: Use different drivers per environment:
config(['scout.driver' => env('SCOUT_DRIVER', 'collection')]);
Driver-Specific Keys:
Ensure all required keys are set in .env (e.g., ALGOLIA_APP_ID, MEILI_MASTER_KEY).
How can I help you explore Laravel packages today?