Skip to content

Index-model field mapping

Define what gets indexed. Map your fields. Embed your relationships. Your index, your rules.

By default, the Index Model indexes every field it finds on the Base Model during sync. To be explicit about what gets indexed, define a fieldMap():

use PDPhilip\ElasticLens\Builder\IndexBuilder;
use PDPhilip\ElasticLens\Builder\IndexField;
class IndexedUser extends IndexModel
{
protected $baseModel = User::class;
public function fieldMap(): IndexBuilder
{
return IndexBuilder::map(User::class, function (IndexField $field) {
$field->text('first_name');
$field->text('last_name');
$field->text('email');
$field->bool('is_active'); //See attributes as fields
$field->type('state', UserState::class); //Maps enum
$field->text('created_at');
$field->text('updated_at');
});
}

If your Base Model has computed attributes you want searchable, map them like regular fields.

For example, $field->bool('is_active') could come from a custom attribute on the Base Model:

App\Models\User.php
// @property-read bool is_active
public function getIsActiveAttribute(): bool
{
return $this->updated_at >= Carbon::now()->modify('-30 days');
}

These values are stored in Elasticsearch as they were at sync time. They’re snapshots, not live calculations.


Here’s the standout feature. Embed relationships into your Index Model and search across them as nested objects. No JOINs, no multiple queries.

Consider these relationships around the User model:

Diagram

User has many Profiles:

use PDPhilip\ElasticLens\Builder\IndexBuilder;
use PDPhilip\ElasticLens\Builder\IndexField;
class IndexedUser extends IndexModel
{
protected $baseModel = User::class;
public function fieldMap(): IndexBuilder
{
return IndexBuilder::map(User::class, function (IndexField $field) {
$field->text('first_name');
$field->text('last_name');
$field->text('email');
$field->bool('is_active');
$field->type('type', UserType::class);
$field->type('state', UserState::class);
$field->text('created_at');
$field->text('updated_at');
$field->embedsMany('profiles', Profile::class)->embedMap(function (IndexField $field) {
$field->text('profile_name');
$field->text('about');
$field->array('profile_tags');
});
});
}

Profile has one ProfileStatus:

use PDPhilip\ElasticLens\Builder\IndexBuilder;
use PDPhilip\ElasticLens\Builder\IndexField;
class IndexedUser extends IndexModel
{
protected $baseModel = User::class;
public function fieldMap(): IndexBuilder
{
return IndexBuilder::map(User::class, function (IndexField $field) {
$field->text('first_name');
$field->text('last_name');
$field->text('email');
$field->bool('is_active');
$field->type('type', UserType::class);
$field->type('state', UserState::class);
$field->text('created_at');
$field->text('updated_at');
$field->embedsMany('profiles', Profile::class)->embedMap(function (IndexField $field) {
$field->text('profile_name');
$field->text('about');
$field->array('profile_tags');
$field->embedsOne('status', ProfileStatus::class)->embedMap(function (IndexField $field) {
$field->text('id');
$field->text('status');
});
});
});
}

User belongs to an Account:

use PDPhilip\ElasticLens\Builder\IndexBuilder;
use PDPhilip\ElasticLens\Builder\IndexField;
class IndexedUser extends IndexModel
{
protected $baseModel = User::class;
public function fieldMap(): IndexBuilder
{
return IndexBuilder::map(User::class, function (IndexField $field) {
$field->text('first_name');
$field->text('last_name');
$field->text('email');
$field->bool('is_active');
$field->type('type', UserType::class);
$field->type('state', UserState::class);
$field->text('created_at');
$field->text('updated_at');
$field->embedsMany('profiles', Profile::class)->embedMap(function (IndexField $field) {
$field->text('profile_name');
$field->text('about');
$field->array('profile_tags');
$field->embedsOne('status', ProfileStatus::class)->embedMap(function (IndexField $field) {
$field->text('id');
$field->text('status');
});
});
$field->embedsBelongTo('account', Account::class)->embedMap(function (IndexField $field) {
$field->text('name');
$field->text('url');
});
});
}

User belongs to Country, but countries don’t change often enough to justify observing:

use PDPhilip\ElasticLens\Builder\IndexBuilder;
use PDPhilip\ElasticLens\Builder\IndexField;
class IndexedUser extends IndexModel
{
protected $baseModel = User::class;
public function fieldMap(): IndexBuilder
{
return IndexBuilder::map(User::class, function (IndexField $field) {
$field->text('first_name');
$field->text('last_name');
$field->text('email');
$field->bool('is_active');
$field->type('type', UserType::class);
$field->type('state', UserState::class);
$field->text('created_at');
$field->text('updated_at');
$field->embedsMany('profiles', Profile::class)->embedMap(function (IndexField $field) {
$field->text('profile_name');
$field->text('about');
$field->array('profile_tags');
$field->embedsOne('status', ProfileStatus::class)->embedMap(function (IndexField $field) {
$field->text('id');
$field->text('status');
});
});
$field->embedsBelongTo('account', Account::class)->embedMap(function (IndexField $field) {
$field->text('name');
$field->text('url');
});
$field->embedsBelongTo('country', Country::class)->embedMap(function (IndexField $field) {
$field->text('country_code');
$field->text('name');
$field->text('currency');
})->dontObserve(); // Don't observe changes in the country model
});
}

User has many UserLogs and you only want the last 10:

use PDPhilip\ElasticLens\Builder\IndexBuilder;
use PDPhilip\ElasticLens\Builder\IndexField;
class IndexedUser extends IndexModel
{
protected $baseModel = User::class;
public function fieldMap(): IndexBuilder
{
return IndexBuilder::map(User::class, function (IndexField $field) {
$field->text('first_name');
$field->text('last_name');
$field->text('email');
$field->bool('is_active');
$field->type('type', UserType::class);
$field->type('state', UserState::class);
$field->text('created_at');
$field->text('updated_at');
$field->embedsMany('profiles', Profile::class)->embedMap(function (IndexField $field) {
$field->text('profile_name');
$field->text('about');
$field->array('profile_tags');
$field->embedsOne('status', ProfileStatus::class)->embedMap(function (IndexField $field) {
$field->text('id');
$field->text('status');
});
});
$field->embedsBelongTo('account', Account::class)->embedMap(function (IndexField $field) {
$field->text('name');
$field->text('url');
});
$field->embedsBelongTo('country', Country::class)->embedMap(function (IndexField $field) {
$field->text('country_code');
$field->text('name');
$field->text('currency');
})->dontObserve(); // Don't observe changes in the country model
$field->embedsMany('logs', UserLog::class, null, null, function ($query) {
$query->orderBy('created_at', 'desc')->limit(10); // Limit the logs to the 10 most recent
})->embedMap(function (IndexField $field) {
$field->text('title');
$field->text('ip');
$field->array('log_data');
});
});
}
  • text($field)
  • integer($field)
  • array($field)
  • bool($field)
  • type($field, $type) - Set own type (like Enums)
  • embedsMany($field, $relatedModelClass, $whereRelatedField, $equalsLocalField, $query)
  • embedsBelongTo($field, $relatedModelClass, $whereRelatedField, $equalsLocalField, $query)
  • embedsOne($field, $relatedModelClass, $whereRelatedField, $equalsLocalField, $query)
  • embedMap(function (IndexField $field) {}) - Define the mapping for the embedded relationship
  • dontObserve() - Don’t observe changes in the related model