ElasticLens
Search your Laravel models with Eloquent ease and Elasticsearch power
Scout’s simplicity • Elasticsearch’s power • Your rules
// Add a trait. Search your models.User::search('loves espressos');// Except you're not stuck with basic text search.User::viaIndex() ->searchPhrase('loves espressos') ->where('status', 'active') ->where('logs.country', 'Norway') ->orderByDesc('created_at') ->paginate(10);// Geo queries. Fuzzy matching. Regex. Aggregations. On your Laravel models.User::viaIndex() ->whereGeoDistance('home.location', '5km', [40.7128, -74.0060]) ->orderByGeo('home.location', [40.7128, -74.0060]) ->get();Scout gives you a search box behind a black box. ElasticLens gives you a search engine you can open up.
Every index is a real Eloquent model you own. You define the field mappings. You define the Elasticsearch schema. You see exactly what’s indexed and how. No magic, no guessing, no driver abstraction sitting between you and your data.
The Query Boundary
Section titled “The Query Boundary”viaIndex() is the gateway. Everything after it runs against the full Elasticsearch query builder. Results come back as your Laravel models.
User::viaIndex() // Cross into Elasticsearch ->searchTerm('max') // full-text search ->where('state', 'active') // familiar Eloquent syntax ->where('logs.country', 'Norway') // query embedded relations with dot notation ->orderByDesc('created_at') // ordering, pagination, aggregations ->paginate(10); // Returns: Paginator of User modelsEverything between viaIndex() and get()/paginate() is the Elasticsearch query builder from Laravel-Elasticsearch. Full-text search, geo queries, fuzzy matching, regex, nested objects, aggregations, highlighting. All of it.
Embed Relationships Into Your Index
Section titled “Embed Relationships Into Your Index”Here’s the magic. Flatten your relational data into Elasticsearch and search across everything with dot notation.
Define what gets embedded in your index model:
class IndexedUser extends IndexModel{ protected $baseModel = User::class;
public function fieldMap(): IndexBuilder { return IndexBuilder::map(User::class, function (IndexField $field) { $field->text('name'); $field->text('email'); $field->embedsMany('profiles', Profile::class)->embedMap(function ($field) { $field->text('bio'); $field->array('tags'); }); $field->embedsBelongTo('company', Company::class)->embedMap(function ($field) { $field->text('name'); $field->text('industry'); }); $field->embedsMany('logs', UserLog::class, null, null, function ($query) { $query->orderBy('created_at', 'desc')->limit(10); })->embedMap(function ($field) { $field->text('action'); }); }); }}Then search across all of it:
User::viaIndex()->where('profiles.bio', 'like', '%elasticsearch%')->get();Related models are observed too. Update a Profile and the parent IndexedUser rebuilds automatically.
Features
Section titled “Features”- Auto-sync - Every create, update, and delete on your base model is reflected in the index. Automatically.
- Embedded relations - Flatten hasMany, hasOne, belongsTo into searchable nested objects
- Full Elasticsearch query builder - Term, phrase, fuzzy, regex, geo, nested, aggregations, highlighting. All returning your Laravel models.
- Index migrations - Define your Elasticsearch mapping with a Blueprint, just like database migrations
- Conditional indexing - Control which records get indexed with
excludeIndex() - Soft delete support - Configure whether soft-deleted records keep their index
- CLI tools - Status, health, build, migrate, and make commands
Requirements
Section titled “Requirements”| Version | |
|---|---|
| PHP | 8.2+ |
| Laravel | 10 / 11 / 12 / 13 |
| Elasticsearch | 8.x + |