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 Elasticsearch Laravel Package

james.xue/laravel-elasticsearch

A Laravel package for integrating Elasticsearch into your application, offering a convenient way to build and run Elasticsearch queries from PHP. It aims to simplify index management and search workflows within Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the config, then point it at a cluster before writing any application code:

composer require xiaoxuan6/laravel-elasticsearch
php artisan vendor:publish --tag=elasticsearch   # confirm the tag in the package's config/ dir

Set the connection in .env (ELASTICSEARCH_HOST, port/user/pass, or a full URL) and confirm the shape of config/elasticsearch.php. The first thing to run is a connectivity smoke test, not a real query:

use Elastic\Elasticsearch\Client;

$info = app(Client::class)->info();   // 2.3.1: namespaced under Elastic\Elasticsearch

Where to look first:

  1. The config file — it is the only place the underlying elasticsearch/elasticsearch client options are exposed, so it tells you what the package supports out of the box vs. what you must build yourself.
  2. The service provider — check whether the client is registered as a singleton and whether a facade alias exists. Injecting the resolved client is more predictable than relying on a facade, especially in queued jobs.
  3. The package's test suite / source for any thin wrapper methods it adds over the raw client.

First realistic use case: index an Eloquent model and run one search from a controller. Get that round trip working (write → refresh → search → hit) before designing indices, because it validates host config, auth, TLS, and version compatibility all at once.

Note on 2.3.1: this release locks the dependency to the 8.x client line and follows the elasticsearch-php 8.0 namespace reorganisation. If your app currently references Elasticsearch\Client / Elasticsearch\ClientBuilder, that is now Elastic\Elasticsearch\Client / Elastic\Elasticsearch\ClientBuilder. Treat the upgrade as a namespace migration, not a patch bump.

Implementation Patterns

Inject the client, wrap it once. Don't scatter raw client calls through controllers. Bind a small repository/service around the client so query construction, index naming, and error translation live in one place:

final class ProductSearch
{
    public function __construct(private Client $client) {}

    public function search(string $q, int $size = 20): array
    {
        return $this->client->search([
            'index' => $this->index(),
            'body'  => ['query' => ['multi_match' => ['query' => $q, 'fields' => ['name^3', 'description']]]],
            'size'  => $size,
        ])->asArray();
    }
}

Index naming per environment/tenant. Derive the index name from config plus an environment or tenant suffix (products_production, products_tenant_42) in one helper. This makes blue/green reindexing and cleanup tractable and prevents local runs from writing into shared indices.

Model observers for writes, queue for backfills. Use saved/deleted model events to push single-document updates, and a queued job for bulk backfills. For bulk, use the bulk API with chunked array_chunk($docs, 500) rather than one request per model — the per-request overhead dominates otherwise. Make the job idempotent (index with an explicit _id derived from the primary key rather than create), so retries don't produce duplicates or 409s.

Keep index creation in migrations or a command, not in request paths. A dedicated artisan command (or migration) that creates mappings and settings is easier to review and re-run than lazy creation on first write. Explicit mappings beat dynamic mapping for anything you will sort or aggregate on.

Testing. Bind a fake against Elastic\Elasticsearch\Client in the container for unit tests so feature tests never touch a live cluster; reserve integration tests for the real client, and hit a throwaway index that the test setup deletes in tearDown. Assert on the query body passed to the client rather than on search relevance — relevance assertions are brittle.

Health and failure handling. Wrap client calls in a service that catches client exceptions and degrades (empty results + log) rather than letting an Elasticsearch outage 500 the whole page. Elasticsearch should be an accelerator, not a hard dependency, for most read paths.

Gotchas and Tips

  • Breaking namespace change (2.3.1). The package now pins elasticsearch-php to 8.x. Elasticsearch\Client, Elasticsearch\ClientBuilder, and most Elasticsearch\Common\Exceptions\* references must become Elastic\Elasticsearch\* / Elastic\Elasticsearch\Exception\*. Grep the whole app, including tests, factories, and any custom service providers, before bumping.
  • Exception handling is different in 8.x. Missing404Exception-style classes no longer exist; you catch ClientResponseException and inspect getResponse()->getStatusCode(). Any catch (Missing404Exception $e) blocks will silently stop matching — a particularly nasty failure mode because it fails open.
  • Config keys moved. The 8.x client is PSR-18 based; options like handler from the 7.x/RingPHP era are gone and replaced by PSR-18 HTTP client wiring. If you published the config under 2.3.0 or earlier, diff it against the new package config rather than assuming it still applies.
  • Version lock is the real constraint. Pinning to 8.x means any other package in the app requiring the 7.x client blocks the upgrade. Check composer why elasticsearch/elasticsearch before assuming a smooth bump, and be aware the server side must be 7.x/8.x-compatible.
  • PHP requirement. The 8.x client raises the minimum PHP version; confirm your CI matrix and any legacy service still on an older runtime before rolling out.
  • Read-your-writes. Elasticsearch is near-real-time. In tests and in any flow that writes then immediately reads (e.g. a "save and preview" screen), pass refresh explicitly or tolerate staleness. Don't add refresh: true to production write paths as a habit — it has a real indexing cost.
  • Silent partial failures in bulk. A 200 from the bulk endpoint does not mean every document indexed; per-item errors live in items[].error. Always inspect the response and log/retry failures. This is the single most common source of "why is this record missing from search".
  • Result depth. from/size pagination breaks past index.max_result_window (default 10,000). For deep pagination use search_after with a stable sort, and make that a deliberate design choice rather than a bug report later.
  • Debugging queries. Log the request body your wrapper sends before it reaches the client, and compare against a direct curl to _search when results look wrong. Distinguishing "bad query" from "bad mapping" from "stale index" is much faster with the body in hand.
  • Extension point. If the package's config doesn't expose an option you need (custom HTTP client, retries, sniffing, connection pool settings), build the client yourself with ClientBuilder in a service provider and bind it to the container — the package resolves the client from the container, so an explicit binding typically overrides it cleanly and keeps you off a fork.
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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator