Introduction
Laravel Scout provides a simple, driver based solution for adding full-text search to your Eloquent models. Using model observers, Scout will automatically keep your search indexes in sync with your Eloquent records.
Currently, Scout ships with Algolia, Meilisearch,
Typesense, and MySQL
/ PostgreSQL (database
) drivers. In
addition, Scout includes a "collection" driver
that is designed for local development usage and does
not require any external dependencies or third-party
services. Furthermore, writing custom drivers is simple
and you are free to extend Scout with your own search
implementations.
Installation
First, install Scout via the Composer package manager:
composer require laravel/scout
After installing Scout, you should publish the Scout
configuration file using the vendor:publish
Artisan command. This command will publish the
scout.php
configuration file to your
application's config
directory:
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
Finally, add the Laravel\Scout\Searchable
trait to the model you would like to make searchable.
This trait will register a model observer that will
automatically keep the model in sync with your search
driver:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
}
Queueing
While not strictly required to use Scout, you should strongly consider configuring a queue driver before using the library. Running a queue worker will allow Scout to queue all operations that sync your model information to your search indexes, providing much better response times for your application's web interface.
Once you have configured a queue driver, set the value of
the queue
option in your
config/scout.php
configuration file to
true
:
'queue' => true,
Even when the queue
option is set to
false
, it's important to remember that some
Scout drivers like Algolia and Meilisearch always index
records asynchronously. Meaning, even though the index
operation has completed within your Laravel application,
the search engine itself may not reflect the new and
updated records immediately.
To specify the connection and queue that your Scout jobs
utilize, you may define the queue
configuration option as an array:
'queue' => [
'connection' => 'redis',
'queue' => 'scout'
],
Of course, if you customize the connection and queue that Scout jobs utilize, you should run a queue worker to process jobs on that connection and queue:
php artisan queue:work redis --queue=scout
Driver Prerequisites
Algolia
When using the Algolia driver, you should configure your
Algolia id
and secret
credentials in your config/scout.php
configuration file. Once your credentials have been
configured, you will also need to install the Algolia
PHP SDK via the Composer package manager:
composer require algolia/algoliasearch-client-php
Meilisearch
Meilisearch is a blazingly fast and open source search engine. If you aren't sure how to install Meilisearch on your local machine, you may use Laravel Sail, Laravel's officially supported Docker development environment.
When using the Meilisearch driver you will need to install the Meilisearch PHP SDK via the Composer package manager:
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
Then, set the SCOUT_DRIVER
environment
variable as well as your Meilisearch host
and key
credentials within your
application's .env
file:
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
For more information regarding Meilisearch, please consult the Meilisearch documentation.
In addition, you should ensure that you install a version
of meilisearch/meilisearch-php
that is
compatible with your Meilisearch binary version by
reviewing Meilisearch's
documentation regarding binary
compatibility.
Warning!
When upgrading Scout on an application that utilizes Meilisearch, you should always review any additional breaking changes to the Meilisearch service itself.
Typesense
Typesense is a lightning-fast, open source search engine and supports keyword search, semantic search, geo search, and vector search.
You can self-host Typesense or use Typesense Cloud.
To get started using Typesense with Scout, install the Typesense PHP SDK via the Composer package manager:
composer require typesense/typesense-php
Then, set the SCOUT_DRIVER
environment
variable as well as your Typesense host and API key
credentials within your application's .env file:
SCOUT_DRIVER=typesense
TYPESENSE_API_KEY=masterKey
TYPESENSE_HOST=localhost
If needed, you may also specify your installation's port, path, and protocol:
TYPESENSE_PORT=8108
TYPESENSE_PATH=
TYPESENSE_PROTOCOL=http
Additional settings and schema definitions for your
Typesense collections can be found within your
application's config/scout.php
configuration file. For more information regarding
Typesense, please consult the Typesense
documentation.
Preparing Data for Storage in Typesense
When utilizing Typesense, your searchable model's must
define a toSearchableArray
method that
casts your model's primary key to a string and creation
date to a UNIX timestamp:
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray()
{
return array_merge($this->toArray(),[
'id' => (string) $this->id,
'created_at' => $this->created_at->timestamp,
]);
}
You should also define your Typesense collection schemas
in your application's config/scout.php
file. A collection schema describes the data types of
each field that is searchable via Typesense. For more
information on all available schema options, please
consult the Typesense
documentation.
If you need to change your Typesense collection's schema
after it has been defined, you may either run
scout:flush
and scout:import
,
which will delete all existing indexed data and recreate
the schema. Or, you may use Typesense's API to modify
the collection's schema without removing any indexed
data.
If your searchable model is soft deletable, you should
define a __soft_deleted
field in the
model's corresponding Typesense schema within your
application's config/scout.php
configuration file:
User::class => [
'collection-schema' => [
'fields' => [
// ...
[
'name' => '__soft_deleted',
'type' => 'int32',
'optional' => true,
],
],
],
],
Dynamic Search Parameters
Typesense allows you to modify your search
parameters dynamically when performing a search
operation via the options
method:
use App\Models\Todo;
Todo::search('Groceries')->options([
'query_by' => 'title, description'
])->get();
Configuration
Configuring Model Indexes
Each Eloquent model is synced with a given search
"index", which contains all of the searchable
records for that model. In other words, you can think of
each index like a MySQL table. By default, each model
will be persisted to an index matching the model's
typical "table" name. Typically, this is the
plural form of the model name; however, you are free to
customize the model's index by overriding the
searchableAs
method on the model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
/**
* Get the name of the index associated with the model.
*/
public function searchableAs(): string
{
return 'posts_index';
}
}
Configuring Searchable Data
By default, the entire toArray
form of a
given model will be persisted to its search index. If
you would like to customize the data that is
synchronized to the search index, you may override the
toSearchableArray
method on the model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
$array = $this->toArray();
// Customize the data array...
return $array;
}
}
Some search engines such as Meilisearch will only perform
filter operations (>
, <
,
etc.) on data of the correct type. So, when using these
search engines and customizing your searchable data, you
should ensure that numeric values are cast to their
correct type:
public function toSearchableArray()
{
return [
'id' => (int) $this->id,
'name' => $this->name,
'price' => (float) $this->price,
];
}
Configuring Filterable Data and Index Settings (Meilisearch)
Unlike Scout's other drivers, Meilisearch requires you to pre-define index search settings such as filterable attributes, sortable attributes, and other supported settings fields.
Filterable attributes are any attributes you plan to
filter on when invoking Scout's where
method, while sortable attributes are any attributes you
plan to sort by when invoking Scout's
orderBy
method. To define your index
settings, adjust the index-settings
portion
of your meilisearch
configuration entry in
your application's scout
configuration
file:
use App\Models\User;
use App\Models\Flight;
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null),
'index-settings' => [
User::class => [
'filterableAttributes'=> ['id', 'name', 'email'],
'sortableAttributes' => ['created_at'],
// Other settings fields...
],
Flight::class => [
'filterableAttributes'=> ['id', 'destination'],
'sortableAttributes' => ['updated_at'],
],
],
],
If the model underlying a given index is soft deletable
and is included in the index-settings
array, Scout will automatically include support for
filtering on soft deleted models on that index. If you
have no other filterable or sortable attributes to
define for a soft deletable model index, you may simply
add an empty entry to the index-settings
array for that model:
'index-settings' => [
Flight::class => []
],
After configuring your application's index settings, you
must invoke the scout:sync-index-settings
Artisan command. This command will inform Meilisearch of
your currently configured index settings. For
convenience, you may wish to make this command part of
your deployment process:
php artisan scout:sync-index-settings
Configuring the Model ID
By default, Scout will use the primary key of the model
as the model's unique ID / key that is stored in the
search index. If you need to customize this behavior,
you may override the getScoutKey
and the
getScoutKeyName
methods on the model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class User extends Model
{
use Searchable;
/**
* Get the value used to index the model.
*/
public function getScoutKey(): mixed
{
return $this->email;
}
/**
* Get the key name used to index the model.
*/
public function getScoutKeyName(): mixed
{
return 'email';
}
}
Configuring Search Engines per Model
When searching, Scout will typically use the default
search engine specified in your application's
scout
configuration file. However, the
search engine for a particular model can be changed by
overriding the searchableUsing
method on
the model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Engines\Engine;
use Laravel\Scout\EngineManager;
use Laravel\Scout\Searchable;
class User extends Model
{
use Searchable;
/**
* Get the engine used to index the model.
*/
public function searchableUsing(): Engine
{
return app(EngineManager::class)->engine('meilisearch');
}
}
Identifying Users
Scout also allows you to auto identify users when using
Algolia. Associating
the authenticated user with search operations may be
helpful when viewing your search analytics within
Algolia's dashboard. You can enable user identification
by defining a SCOUT_IDENTIFY
environment
variable as true
in your application's
.env
file:
SCOUT_IDENTIFY=true
Enabling this feature will also pass the request's IP address and your authenticated user's primary identifier to Algolia so this data is associated with any search request that is made by the user.
Database / Collection Engines
Database Engine
Warning!
The database engine currently supports MySQL and PostgreSQL.
If your application interacts with small to medium sized databases or has a light workload, you may find it more convenient to get started with Scout's "database" engine. The database engine will use "where like" clauses and full text indexes when filtering results from your existing database to determine the applicable search results for your query.
To use the database engine, you may simply set the value
of the SCOUT_DRIVER
environment variable to
database
, or specify the
database
driver directly in your
application's scout
configuration file:
SCOUT_DRIVER=database
Once you have specified the database engine as your preferred driver, you must configure your searchable data. Then, you may start executing search queries against your models. Search engine indexing, such as the indexing needed to seed Algolia, Meilisearch or Typesense indexes, is unnecessary when using the database engine.
Customizing Database Searching Strategies
By default, the database engine will execute a
"where like" query against every model
attribute that you have configured as
searchable. However, in some situations, this
may result in poor performance. Therefore, the database
engine's search strategy can be configured so that some
specified columns utilize full text search queries or
only use "where like" constraints to search
the prefixes of strings (example%
) instead
of searching within the entire string
(%example%
).
To define this behavior, you may assign PHP attributes to
your model's toSearchableArray
method. Any
columns that are not assigned additional search strategy
behavior will continue to use the default "where
like" strategy:
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
#[SearchUsingPrefix(['id', 'email'])]
#[SearchUsingFullText(['bio'])]
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'bio' => $this->bio,
];
}
Warning!
Before specifying that a column should use full text query constraints, ensure that the column has been assigned a full text index.
Collection Engine
While you are free to use the Algolia, Meilisearch, or Typesense search engines during local development, you may find it more convenient to get started with the "collection" engine. The collection engine will use "where" clauses and collection filtering on results from your existing database to determine the applicable search results for your query. When using this engine, it is not necessary to "index" your searchable models, as they will simply be retrieved from your local database.
To use the collection engine, you may simply set the
value of the SCOUT_DRIVER
environment
variable to collection
, or specify the
collection
driver directly in your
application's scout
configuration file:
SCOUT_DRIVER=collection
Once you have specified the collection driver as your preferred driver, you may start executing search queries against your models. Search engine indexing, such as the indexing needed to seed Algolia, Meilisearch, or Typesense indexes, is unnecessary when using the collection engine.
Differences From Database Engine
On first glance, the "database" and
"collections" engines are fairly similar. They
both interact directly with your database to retrieve
search results. However, the collection engine does not
utilize full text indexes or LIKE
clauses
to find matching records. Instead, it pulls all possible
records and uses Laravel's Str::is
helper
to determine if the search string exists within the
model attribute values.
The collection engine is the most portable search engine as it works across all relational databases supported by Laravel (including SQLite and SQL Server); however, it is less efficient than Scout's database engine.
Indexing
Batch Import
If you are installing Scout into an existing project, you
may already have database records you need to import
into your indexes. Scout provides a
scout:import
Artisan command that you may
use to import all of your existing records into your
search indexes:
php artisan scout:import "App\Models\Post"
The flush
command may be used to remove all
of a model's records from your search indexes:
php artisan scout:flush "App\Models\Post"
Modifying the Import Query
If you would like to modify the query that is used to
retrieve all of your models for batch importing, you may
define a makeAllSearchableUsing
method on
your model. This is a great place to add any eager
relationship loading that may be necessary before
importing your models:
use Illuminate\Database\Eloquent\Builder;
/**
* Modify the query used to retrieve models when making all of the models searchable.
*/
protected function makeAllSearchableUsing(Builder $query): Builder
{
return $query->with('author');
}
Warning!
ThemakeAllSearchableUsing
method may not be applicable when using a queue to batch import models. Relationships are not restored when model collections are processed by jobs.
Adding Records
Once you have added the
Laravel\Scout\Searchable
trait to a model,
all you need to do is save
or
create
a model instance and it will
automatically be added to your search index. If you have
configured Scout to use queues
this operation will be performed in the background by
your queue worker:
use App\Models\Order;
$order = new Order;
// ...
$order->save();
Adding Records via Query
If you would like to add a collection of models to your
search index via an Eloquent query, you may chain the
searchable
method onto the Eloquent query.
The searchable
method will chunk the
results of the query and add the records to your
search index. Again, if you have configured Scout to use
queues, all of the chunks will be imported in the
background by your queue workers:
use App\Models\Order;
Order::where('price', '>', 100)->searchable();
You may also call the searchable
method on
an Eloquent relationship instance:
$user->orders()->searchable();
Or, if you already have a collection of Eloquent models
in memory, you may call the searchable
method on the collection instance to add the model
instances to their corresponding index:
$orders->searchable();
Note:
Thesearchable
method can be considered an "upsert" operation. In other words, if the model record is already in your index, it will be updated. If it does not exist in the search index, it will be added to the index.
Updating Records
To update a searchable model, you only need to update the
model instance's properties and save
the
model to your database. Scout will automatically persist
the changes to your search index:
use App\Models\Order;
$order = Order::find(1);
// Update the order...
$order->save();
You may also invoke the searchable
method on
an Eloquent query instance to update a collection of
models. If the models do not exist in your search index,
they will be created:
Order::where('price', '>', 100)->searchable();
If you would like to update the search index records for
all of the models in a relationship, you may invoke the
searchable
on the relationship
instance:
$user->orders()->searchable();
Or, if you already have a collection of Eloquent models
in memory, you may call the searchable
method on the collection instance to update the model
instances in their corresponding index:
$orders->searchable();
Modifying Records Before Importing
Sometimes you may need to prepare the collection of
models before they are made searchable. For instance,
you may want to eager load a relationship so that the
relationship data can be efficiently added to your
search index. To accomplish this, define a
makeSearchableUsing
method on the
corresponding model:
use Illuminate\Database\Eloquent\Collection;
/**
* Modify the collection of models being made searchable.
*/
public function makeSearchableUsing(Collection $models): Collection
{
return $models->load('author');
}
Removing Records
To remove a record from your index you may simply
delete
the model from the database. This
may be done even if you are using soft deleted
models:
use App\Models\Order;
$order = Order::find(1);
$order->delete();
If you do not want to retrieve the model before deleting
the record, you may use the unsearchable
method on an Eloquent query instance:
Order::where('price', '>', 100)->unsearchable();
If you would like to remove the search index records for
all of the models in a relationship, you may invoke the
unsearchable
on the relationship
instance:
$user->orders()->unsearchable();
Or, if you already have a collection of Eloquent models
in memory, you may call the unsearchable
method on the collection instance to remove the model
instances from their corresponding index:
$orders->unsearchable();
Pausing Indexing
Sometimes you may need to perform a batch of Eloquent
operations on a model without syncing the model data to
your search index. You may do this using the
withoutSyncingToSearch
method. This method
accepts a single closure which will be immediately
executed. Any model operations that occur within the
closure will not be synced to the model's index:
use App\Models\Order;
Order::withoutSyncingToSearch(function () {
// Perform model actions...
});
Conditionally Searchable Model Instances
Sometimes you may need to only make a model searchable
under certain conditions. For example, imagine you have
App\Models\Post
model that may be in one of
two states: "draft" and "published".
You may only want to allow "published" posts
to be searchable. To accomplish this, you may define a
shouldBeSearchable
method on your
model:
/**
* Determine if the model should be searchable.
*/
public function shouldBeSearchable(): bool
{
return $this->isPublished();
}
The shouldBeSearchable
method is only
applied when manipulating models through the
save
and create
methods,
queries, or relationships. Directly making models or
collections searchable using the searchable
method will override the result of the
shouldBeSearchable
method.
Warning!
TheshouldBeSearchable
method is not applicable when using Scout's "database" engine, as all searchable data is always stored in the database. To achieve similar behavior when using the database engine, you should use where clauses instead.
Searching
You may begin searching a model using the
search
method. The search method accepts a
single string that will be used to search your models.
You should then chain the get
method onto
the search query to retrieve the Eloquent models that
match the given search query:
use App\Models\Order;
$orders = Order::search('Star Trek')->get();
Since Scout searches return a collection of Eloquent models, you may even return the results directly from a route or controller and they will automatically be converted to JSON:
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/search', function (Request $request) {
return Order::search($request->search)->get();
});
If you would like to get the raw search results before
they are converted to Eloquent models, you may use the
raw
method:
$orders = Order::search('Star Trek')->raw();
Custom Indexes
Search queries will typically be performed on the index
specified by the model's searchableAs
method. However, you may use the within
method to specify a custom index that should be searched
instead:
$orders = Order::search('Star Trek')
->within('tv_shows_popularity_desc')
->get();
Where Clauses
Scout allows you to add simple "where" clauses to your search queries. Currently, these clauses only support basic numeric equality checks and are primarily useful for scoping search queries by an owner ID:
use App\Models\Order;
$orders = Order::search('Star Trek')->where('user_id', 1)->get();
In addition, the whereIn
method may be used
to verify that a given column's value is contained
within the given array:
$orders = Order::search('Star Trek')->whereIn(
'status', ['open', 'paid']
)->get();
The whereNotIn
method verifies that the
given column's value is not contained in the given
array:
$orders = Order::search('Star Trek')->whereNotIn(
'status', ['closed']
)->get();
Since a search index is not a relational database, more advanced "where" clauses are not currently supported.
Warning!
If your application is using Meilisearch, you must configure your application's filterable attributes before utilizing Scout's "where" clauses.
Pagination
In addition to retrieving a collection of models, you may
paginate your search results using the
paginate
method. This method will return an
Illuminate\Pagination\LengthAwarePaginator
instance just as if you had paginated a traditional
Eloquent query:
use App\Models\Order;
$orders = Order::search('Star Trek')->paginate();
You may specify how many models to retrieve per page by
passing the amount as the first argument to the
paginate
method:
$orders = Order::search('Star Trek')->paginate(15);
Once you have retrieved the results, you may display the results and render the page links using Blade just as if you had paginated a traditional Eloquent query:
<div class="container">
@foreach ($orders as $order)
{{ $order->price }}
@endforeach
</div>
{{ $orders->links() }}
Of course, if you would like to retrieve the pagination results as JSON, you may return the paginator instance directly from a route or controller:
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/orders', function (Request $request) {
return Order::search($request->input('query'))->paginate(15);
});
Warning!
Since search engines are not aware of your Eloquent model's global scope definitions, you should not utilize global scopes in applications that utilize Scout pagination. Or, you should recreate the global scope's constraints when searching via Scout.
Soft Deleting
If your indexed models are soft deleting
and you need to search your soft deleted models, set the
soft_delete
option of the
config/scout.php
configuration file to
true
:
'soft_delete' => true,
When this configuration option is true
,
Scout will not remove soft deleted models from the
search index. Instead, it will set a hidden
__soft_deleted
attribute on the indexed
record. Then, you may use the withTrashed
or onlyTrashed
methods to retrieve the soft
deleted records when searching:
use App\Models\Order;
// Include trashed records when retrieving results...
$orders = Order::search('Star Trek')->withTrashed()->get();
// Only include trashed records when retrieving results...
$orders = Order::search('Star Trek')->onlyTrashed()->get();
Note:
When a soft deleted model is permanently deleted usingforceDelete
, Scout will remove it from the search index automatically.
Customizing Engine Searches
If you need to perform advanced customization of the
search behavior of an engine you may pass a closure as
the second argument to the search
method.
For example, you could use this callback to add
geo-location data to your search options before the
search query is passed to Algolia:
use Algolia\AlgoliaSearch\SearchIndex;
use App\Models\Order;
Order::search(
'Star Trek',
function (SearchIndex $algolia, string $query, array $options) {
$options['body']['query']['bool']['filter']['geo_distance'] = [
'distance' => '1000km',
'location' => ['lat' => 36, 'lon' => 111],
];
return $algolia->search($query, $options);
}
)->get();
Customizing the Eloquent Results Query
After Scout retrieves a list of matching Eloquent models
from your application's search engine, Eloquent is used
to retrieve all of the matching models by their primary
keys. You may customize this query by invoking the
query
method. The query
method
accepts a closure that will receive the Eloquent query
builder instance as an argument:
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;
$orders = Order::search('Star Trek')
->query(fn (Builder $query) => $query->with('invoices'))
->get();
Since this callback is invoked after the relevant models
have already been retrieved from your application's
search engine, the query
method should not
be used for "filtering" results. Instead, you
should use Scout where
clauses.
Custom Engines
Writing the Engine
If one of the built-in Scout search engines doesn't fit
your needs, you may write your own custom engine and
register it with Scout. Your engine should extend the
Laravel\Scout\Engines\Engine
abstract
class. This abstract class contains eight methods your
custom engine must implement:
use Laravel\Scout\Builder;
abstract public function update($models);
abstract public function delete($models);
abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function mapIds($results);
abstract public function map(Builder $builder, $results, $model);
abstract public function getTotalCount($results);
abstract public function flush($model);
You may find it helpful to review the implementations of
these methods on the
Laravel\Scout\Engines\AlgoliaEngine
class.
This class will provide you with a good starting point
for learning how to implement each of these methods in
your own engine.
Registering the Engine
Once you have written your custom engine, you may
register it with Scout using the extend
method of the Scout engine manager. Scout's engine
manager may be resolved from the Laravel service
container. You should call the extend
method from the boot
method of your
App\Providers\AppServiceProvider
class or
any other service provider used by your application:
use App\ScoutExtensions\MySqlSearchEngine;
use Laravel\Scout\EngineManager;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
resolve(EngineManager::class)->extend('mysql', function () {
return new MySqlSearchEngine;
});
}
Once your engine has been registered, you may specify it
as your default Scout driver
in your
application's config/scout.php
configuration file:
'driver' => 'mysql',