Introducing Laravel Scout

Posted on July 29, 2016

Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

Search tools ElasticSearch and Algolia have gained a lot of popularity in the Laravel community in the last few years as powerful tools for indexing and searching your data. Ben Corlett did a fantastic job introducing ElasticSearch at Laracon Eu 2014, and I wrote a pull request to introducing ElasticSearch-based indexing for the docs in 2015. But before my PR was merged, the folks at Algolia took it and updated it to instead use Algolia (faster and with a better UI!), and that's what you'll see today if you search the Laravel docs.

If you take a look at my pull request or theirs, you'll see that it's not a small task to integrate fulltext search into your site. Algolia has since released a free product called Algolia DocSearch that makes it easy to add an Algolia search widget to documentation pages. But for anything else, you're still stuck writing the integration yourself—that is, until now.

Introducing Laravel Scout

Scout is a driver-based fulltext search solution for Eloquent. Scout makes it easy to index and search the contents of your Eloquent models; currently it works with Algolia and ElasticSearch, but Taylor's asked for community contributions to other fulltext search services.

Scout is a separate Laravel package, like Cashier, that you'll need to pull in with Composer. We'll be adding traits to our models that indicate to Scout that it should listen to the events fired when instances of those models are modified and update the search index in response.

Take a look at this syntax for fulltext search, for finding any Review with the word Llew in it:

Review::search('Llew')->where('account_id', 2)->get();

All that with very little configuration. That's a beautiful thing.

Installing Scout

First, pull in the package (once it's live, and on a Laravel 5.3 app):

composer require laravel/scout

Next, add the Scout service provider (Laravel\Scout\ScoutServiceProvider::class) to the providers section of config/app.php.

We'll want to set up our Scout configuration. Run php artisan vendor:publish and paste your Algolia credentials in config/scout.php.

Finally, assuming you're using Algolia, install the Algolia SDK:

composer require algolia/algoliasearch-client-php

Marking your model for indexing

Now, go to your model (we'll use Review, for a book review, for this example). Import the Laravel\Scout\Searchable trait. You can define which properties are searchable using the toSearchableArray() method (it defaults to mirroring toArray()), and define the name of the model's index using the searchableAs() method (it defaults to the table name).

Once we've done this, you can go check out your Algolia index page on their web site; when you add, update, or delete Review records, you'll see your Algolia index update. Just like that.

Searching your index

We took a look at this already, but here's a refresh of how to search:

// Get all records from the Review that match the term "Llew"

// Get all records from the Review that match the term "Llew",
// limited to 20 per page and reading the ?page query parameter,
// just like Eloquent pagination

// Get all records from the Review that match the term "Llew"
// and have an account_id field set to 2
Review::search('Llew')->where('account_id', 2)->get();

What comes back from these searches? A Collection of Eloquent models, re-hydrated from your database. The IDs are stored in Algolia, which returns a list of matched IDs, and then Scout pulls the database records for those and returns them as Eloquent objects.

You don't have full access to the complexity of SQL where commands, but it handles a solid basic framework for comparison checks like you can see in the code samples above.


You can probably guess that we're now making HTTP requests to Algolia on every request that modifies any database records. That can make things slow down very quickly, so you may find yourself wanting to queue these operations—which, thankfully, is simple.

In config/scout.php, set queue to true so that these updates are set to be indexed asynchronously. We're now looking at "eventual consistency"; your database records will receive the updates immediately, and the updates to your search indexes will be queued and updated as fast as your queue worker allows.

Special cases

Let's cover some special cases.

Perform operations without indexing

What if you want to perform a set of operations and avoid triggering the indexing in response? Just wrap them in the withoutSyncingToSearch() method on your model:

Review::withoutSyncingToSearch(function () {
    // make a bunch of reviews, e.g.
    factory(Review::class, 10)->create();

Manually trigger indexing via code

Let's say you're now ready to perform the indexes, now that some bulk operation has been successfully performed. How?

Just add searchable() to the end of any Eloquent query and it will index all of the records that were found in that query.


You can also choose to scope the query to only those you want to index, but it's worth noting that the indexing will insert new records and update old records, so it's not bad to let it run over some records that may be indexed already.

This will also work on a relationship:


You can also un-index any records with the same sort of query chaining, but just using unsearchable() instead:

Review::where('sucky', true)->unsearchable();

Manually trigger indexing via CLI

There's an Artisan command for that.™

php artisan scout:import App\\Review

That'll chunk all of the Review models and index them all.


That's it! With almost no work, you now have complete full-text search running on your Eloquent models.

Comments? I'm @stauffermatt on Twitter

Tags: laravel  •  laravel 5.3  •  laravel scout

(This is part of a series of posts on New Features in Laravel 5.3. )

  1. Jun 16, 2016 | laravel, laravel 5.3, echo, websockets
    Laravel Echo is a tool that makes it easy for you to bring the power of WebSockets to your Laravel applications. It simplifie...
  2. Jun 27, 2016 | laravel, laravel 5.3
    Let's take a look at another new feature that's coming in Laravel 5.3. What are Blade directives? Laravel's Blade templating ...
  3. Jun 29, 2016 | laravel, laravel 5.3, eloquent
    More Laravel 5.3 goodies! This time, it's an update to the Eloquent firstOrCreate method. If you've never used it before, you...
  4. Jul 6, 2016 | laravel, laravel 5.3
    Time for another new feature in Laravel 5.3! Shortcut global helpers in Laravel As I was writing my book I noticed a pattern ...
  5. Jul 8, 2016 | laravel, laravel 5.3, eloquent
    While Laravel has had the ability to cast your data to and from JSON since version 5.0, it was previously just a convenience&...
  6. Jul 8, 2016 | laravel, laravel 5.3
    If you want to filter a Laravel collection to only those records which meet particular criteria, you're most likely going to ...
  7. Jul 25, 2016 | laravel, laravel 5.3
    In Laravel 5.3, we have a new validation option: image dimensions for image uploads. The validation rule is called dimensions...
  8. Jul 26, 2016 | laravel, laravel 5.3
    Laravel's pagination library is brilliant, because pagination is a common task that is a surprising amount of work to impleme...
  9. Jul 27, 2016 | laravel, laravel 5.3, laravel scout, laravel passport, mailable
    THIS POST IS STILL IN ACTIVE PROGRESS Once the videos are released I'll be able to update this post with more info, since I w...
  10. Jul 27, 2016 | laravel, laravel 5.3, routing
    The last few versions of Laravel have showed the way routing works shifting around a bit. This is usually a sign that we're f...
  11. Jul 29, 2016 | laravel, laravel 5.3, laravel scout
    Search tools ElasticSearch and Algolia have gained a lot of popularity in the Laravel community in the last few years as powe...
  12. Jul 30, 2016 | laravel, laravel 5.3, laravel passport, oauth
    API authentication can be tricky. OAuth 2 is the reigning ruler of the various standards that you might consider, but it's co...
  13. Aug 5, 2016 | laravel, laravel 5.3, mail, laravel mailables
    For the longest time, sending mail in Laravel has felt clumsy compared to the relatively light APIs of most other Laravel fea...
  14. Aug 8, 2016 | laravel, laravel 5.3
    We've already covered the new routes directory and the changes that will make to the directory structure. But there's one oth...
  15. Oct 19, 2016 | laravel, laravel 5.3, laravel notifications, notifications
    In a previous blog post I've covered Laravel's new Mailable feature, which introduces some important changes to how we send m...
  16. Dec 21, 2016 | laravel, laravel 5.3, queues
    Queues are one of those tools in Laravel that everyone knows is there, but very few people understand deeply. It's understand...
  17. Dec 21, 2016 | laravel, laravel 5.3, vuejs, vueify, authorization
    In Laravel 5.3, it's easier than ever to write and use Vue components out of the box. This means 5.3 has a somewhat more opin...
  18. Jan 30, 2017 | laravel, laravel 5.3, artisan
    Before Laravel 5.3, defining an Artisan console command—something like php artisan sync:dates—required you to create a ne...


For quick links to fresh content, and more thoughts that don't make it to the blog