algolia/algoliasearch-laravel
Official Algolia integration for Laravel. Index Eloquent models, keep them synced automatically, and add fast, typo-tolerant search to your app, with Scout support, aggregators, and zero-downtime reindexing.
Treat this package as a legacy integration, not a greenfield choice. It is archived, its last release was January 2017, and it targets the Laravel 5.3–5.4 era and algoliasearch-client-php v1. Composer will typically flag it as abandoned and point you at laravel/scout. If you are inheriting a project that already uses it, the goal is to understand the wiring first, then plan a migration.
Minimal steps:
Install (only if already pinned in your project).
composer require algolia/algoliasearch-laravel
Check composer.lock immediately afterward for the resolved algoliasearch-client-php version — this determines which client API the package is actually talking to.
Publish the config.
php artisan vendor:publish --provider="AlgoliaSearch\Laravel\AlgoliaSearchServiceProvider"
This drops config/algolia.php into your app. Read it before doing anything else; it is the single source of truth for which credentials and index settings the package uses.
Register the provider and facade manually. Because the release predates Laravel 5.5 package auto-discovery, you will almost certainly need to add the service provider to config/app.php under providers, and alias the facade under aliases if you intend to use it.
Set credentials in .env — an application ID, a server-side admin API key, and a search-only key. The search-only key is the only one that may ever be shipped to the browser.
First use case: put the package's searchable trait on a single Eloquent model, implement toSearchableArray(), write an artisan command that pushes all records into an index, then run a query against that index from a controller.
Where to look first, in order: config/algolia.php → the service provider (to see what it binds into the container and how the client is constructed) → the searchable trait (to see which model events it hooks) → your own model's toSearchableArray().
Model-as-document with a dedicated projection. The core pattern is a trait on an Eloquent model plus a toSearchableArray() method. Never let the package serialize the raw model — Algolia documents are denormalized and should contain only what you search, facet, filter, and display. Flatten relationships into the array (category names, tag arrays, computed price buckets) rather than relying on joins at query time.
public function toSearchableArray(): array
{
return [
'objectID' => $this->id,
'name' => $this->name,
'brand' => $this->brand->name,
'categories' => $this->categories->pluck('name')->all(),
'price' => (int) $this->price_cents,
'published_at' => $this->published_at?->timestamp,
];
}
Sync on write, reindex in bulk. The trait typically hooks saved/deleted model events. That is fine for CRUD in the admin panel, but it is the wrong tool for imports and backfills. Keep a dedicated artisan command that pages through records in chunks (500–1000) and calls the batch indexing endpoint. Make it idempotent so you can re-run it after changing toSearchableArray().
Two credentials, two call sites. Server-side code (controllers, commands, jobs) uses the admin key for indexing and for privileged queries. Browser-side code uses the search-only key or a secured API key generated server-side when you need per-user filters:
$client->generateSecuredApiKey($searchKey, ['filters' => 'tenant_id:'.$tenant->id]);
Facade for one-offs, container for testability. The facade is convenient in controllers and tinker, but inject the underlying client into services and jobs so you can swap or mock it in tests. In practice most teams wrap the package in their own SearchService and call that everywhere — this pays off enormously at migration time.
Replica indices for sorting. Algolia sorts on replica indices, not on the primary. Set up products_price_asc / products_price_desc as replicas of products, configure ranking on each, and pass the replica name as the index at query time. Do this in index settings, not in application code.
Queue the writes. Indexing inside a request is a latency and failure-mode problem. Push index/update/delete operations onto a queue job, and use Bus::batch() (or a simple chunked command) for backfills so you get retry semantics and progress.
Environment-scoped index names. Prefix indices with the environment (staging_products, products) via config. Sharing an index between staging and production is a classic way to corrupt a live search experience.
The transitive client version is the real risk. The package depends on algoliasearch-client-php, whose v2 release changed the client class and method signatures substantially (AlgoliaSearch\Client → Algolia\AlgoliaSearch\SearchClient::create()). Because the package is unmaintained, a loose constraint can resolve to an incompatible client. Pin the client version explicitly in your root composer.json and verify composer why algoliasearch-client-php before any dependency update.
No auto-discovery. On Laravel 5.5+, don't assume php artisan knows about the provider. If searches return "class not found" or the facade is unresolved, it's almost always a missing manual registration in config/app.php.
config:cache will freeze your env values. The published config file reads from env() at load time. If you cache config and then rotate a key in .env, nothing changes until you re-cache. Never call env() outside config files.
Faceting and filtering require index settings, not just document fields. A field present in toSearchableArray() is not filterable until you set attributesForFaceting (and numericAttributesToIndex for ranges) on the index. This is the single most common "why does my filter return nothing" bug — the answer is nearly always "you didn't set the setting."
The 1,000-hit pagination ceiling. Algolia caps hitsPerPage × page at 1,000 by default. If your UI implies infinite scroll beyond that, you need browse/browseObjects from a server-side admin context, or a different pagination contract.
Soft deletes leak into the index. If your model uses SoftDeletes, the deleted event may not fire the way the trait expects, or restored won't re-add the document. Decide explicitly: either forceDelete on soft-delete, or filter deleted_at IS NULL at query time via a facet. Test both transitions.
Batch size and payload limits. Algolia's batch endpoint has a request-size cap (~10 MB) and per-record size limits. Very large toSearchableArray() payloads (full HTML bodies, base64 blobs) will fail silently or reject the batch. Chunk your imports and strip markup before indexing.
Tests hit a real network API. There is no offline mode. Use a dedicated test application ID and a unique index prefix per test run, then delete the index in tearDown(). Otherwise CI becomes flaky and slow, and parallel test workers clobber each other's indices.
Extension point: wrap, don't patch. You cannot rely on upstream fixes. Put your own SearchService in front of the package's client, keep the trait thin (delegate to the service), and isolate every package-specific call in one namespace. When you migrate to laravel/scout with the Algolia driver — or algolia/scout-extended for replicas, synonyms, and rule-based merchandising — that wrapper becomes the seam you swap behind, and the rest of your application doesn't notice.
Migration is the actual recommendation. Scout ships an Algolia engine out of the box, gives you Searchable, searchable(), unsearchable(), withoutSyncingToSearch(), model observers, and scout:import / scout:flush commands — all maintained. For a codebase of any size, replacing this package with Scout is a few days of work and removes a permanent dependency risk. Budget for it rather than extending the legacy integration.
How can I help you explore Laravel packages today?