Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Scout Elasticsearch Laravel Package

baijunyao/laravel-scout-elasticsearch

A Laravel Scout driver that integrates Elasticsearch as the search engine for Eloquent models. Configure indices, index and search records through Scout, and use Elasticsearch full-text capabilities in Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites (do these before touching the package):

  1. A running Elasticsearch node. The package is written against the modern elasticsearch/elasticsearch PHP client, so pick an ES major version that matches the client constraint in the package's composer.json (7.x is the safest bet for most versions of this package).
  2. The IK analysis plugin for Chinese tokenization. This is not optional if your content is Chinese — the package's default index settings reference ik_max_word / ik_smart, and index creation will fail without it:
    bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.x.x/elasticsearch-analysis-ik-7.x.x.zip
    
  3. Verify: curl -s localhost:9200/_cat/plugins and curl -s localhost:9200/_cat/indices?v.

Install:

composer require laravel/scout baijunyao/laravel-scout-elasticsearch
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"   # config/scout.php

Add the driver block to config/scout.php and switch the default driver:

'driver' => env('SCOUT_DRIVER', 'elasticsearch'),

'elasticsearch' => [
    'index'    => env('ELASTICSEARCH_INDEX', 'laravel'),
    'hosts'    => [env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200')],
    'settings' => [
        'number_of_shards'   => 1,
        'number_of_replicas' => 0,
    ],
],

First use case — index one model and search it:

use Laravel\Scout\Searchable;

class Article extends Model
{
    use Searchable;

    public function searchableAs(): string   { return 'articles'; }
    public function toSearchableArray(): array {
        return [
            'id'      => $this->id,
            'title'   => $this->title,
            'content' => strip_tags($this->content),
        ];
    }
}
php artisan scout:import "App\Models\Article"
Article::search('中文 关键词')->paginate(15);

Where to look first: the package's Engine class (it is the driver registration in the service provider — check Scout::extend('elasticsearch', ...)), then config/scout.php. Everything day-to-day routes through toSearchableArray() and searchableAs().

Implementation Patterns

One index per model (searchableAs). This is how the package delivers "multiple tables": each Eloquent model declares its own index name, so articles, products, and users live in separate ES indices rather than Scout's default single index. Keep the name stable — renaming it orphans a whole index.

Index build lifecycle. Treat indexing as a deploy step, not a runtime side effect:

php artisan scout:flush "App\Models\Article"
php artisan scout:import "App\Models\Article"   # auto-creates the index using your settings

Run flush + import in a release script when mappings or analyzers change. scout:import chunks through makeAllSearchableUsing() (chunkById, typically), so it is safe on large tables — but it is synchronous, so run it from a queue-backed job or a deploy hook, not a web request.

Incremental sync. With the Searchable trait, saved/deleted model events push to ES automatically. In production set SCOUT_QUEUE=true so writes go through the queue instead of blocking the request; then make sure your workers are running and restarted after any config change. Override shouldBeSearchable() to keep drafts/soft-deleted rows out of the index.

Query building. Scout's fluent API maps onto the driver's ES query builder:

Article::search($q)
    ->where('category_id', 3)
    ->where('status', 'published')
    ->orderBy('created_at', 'desc')
    ->take(50)
    ->get();

Chain ->query(fn ($q) => $q->with('author', 'tags')) to eager-load relations — Scout only rehydrates IDs back into Eloquent, so without this you get N+1s on every search page.

Escape hatch for real ES features. Aggregations, highlighters, multi_match with field boosts, and completion suggesters aren't covered by the fluent API. Use ->raw() for the raw response, or grab the underlying client from the engine and issue DSL you write yourself. For anything beyond simple filters, plan on writing DSL.

Analyzer-aware mappings. The package creates indices from the settings block in config/scout.php. Put your analysis (tokenizer = ik_max_word, filters, per-field analyzers) and mappings there so auto-created indices are correct — otherwise you're relying on dynamic mapping, which will happily type your id as a long and your date strings as text.

Gotchas and Tips

  • Mappings are immutable. You cannot change a field's analyzer or type in place. Any change means: create a new index (articles_v2), reindex, swap an alias. Plan the alias from day one — searchableAs() should return an alias, not a physical index, or you'll be forced into downtime later.
  • Missing IK plugin is the #1 failure. analyzer [ik_max_word] not found on scout:import means the plugin isn't installed on every node, or ES wasn't restarted after install.
  • refresh lag. Newly written documents aren't visible for ~1s (default refresh interval). Integration tests that write-then-search will flake — either add a sleep, or issue writes with refresh=wait_for, or set refresh_interval to -1 during bulk import and back afterwards for a large scout:import.
  • toSearchableArray() only returns plain scalars/arrays. Returning Eloquent models, Carbon objects without casting, or HTML-laden content will produce garbage or blow up on mapping. Strip tags, flatten relations, and exclude $appends bloat — every field goes over the wire and into the index.
  • The fluent where() is not SQL. Scopes, whereIn, whereNull, and relation filters do not translate. Ranges, booleans, and nested fields need raw DSL. Don't assume Laravel semantics.
  • Version alignment with Scout. Scout's Engine contract changed across v9/v10 (keys(), paginate() signatures). A 79-star community package will lag; pin laravel/scout to the version the package's composer.json targets, and be ready to vendor-patch. The README and config comments are in Chinese — keep a translation handy for the option names.
  • Debugging. Inspect _cat/indices?v for shard health (a yellow cluster with number_of_replicas: 1 on a single node means the replica is unassigned), _mapping for what actually got created, and _search with "explain": true for scoring surprises. Log the raw response from ->raw() when a query returns the wrong thing — very often the issue is the mapping, not the query.
  • Don't index everything. Exclude binary columns, huge text bodies you never display in results, and timestamps you don't filter on. Index size and reindex time are the main operational costs here.
  • Backups. ES snapshots are outside Laravel's mysqldump habit; a lost index is a scout:import away only if you can rebuild — make sure the canonical data is still in your database.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource