Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Gregoire's avatar

Dynamic indexes using Laravel Scout searchableAs() method

Hi everyone,

This is probably a duplicate of @bobmulder's post from a few years back, but since it didn’t receive a clear answer and I’ve been stuck on this for a while, I’m asking for help again.

Using PostgreSQL’s jsonb capabilities, I’ve built a structure similar to EAV, with sources and records.

I want to index and search records using a dynamic index name based on the $source->index field.

Dynamic searchableAs() approach

This works fine when indexing records as they’re created:

public function searchableAs(): string
{
    return Str::of('source_')->append($this->source->index)->lower();
}

However, running something like php artisan scout:refresh or php artisan scout:import throws the following error:

$ php artisan scout:refresh "App\Models\Records"
Attempt to read property "index" on null

Fallback "dumb" index

To avoid the error, I added a fallback value:

public function searchableAs(): string
{
    if (! $this || ! $this->source) {
        return 'empty_source';
    }

    return Str::of('source_')->append($this->source->index)->lower();
}

This works, but it creates an unwanted and useless empty_source index. Worse, I realized that searchableAs() is only called once during indexing, meaning all records get grouped under the first source's index—completely mixing different datasets.

Is searchableAs() the right approach?

An alternative would be to index all records under a shared index and include the source identifier in toSearchableArray(), then filter results via custom queries. But that feels wrong, as the records are semantically very different and likely belong in separate schemas.

What would be the cleanest and most maintainable way to handle this?

0 likes
3 replies
Gregoire's avatar
Gregoire
OP
Best Answer
Level 1

So, after a lot of back and forth, I ended up extending the Scout Searchable trait and overriding some methods, which seems to work.

If it can help someone:

use Searchable {
    Searchable::queueMakeSearchable as parentQueueMakeSearchable;
}

public function searchableAs(): string
{
    return $this->source?->getSearchKey() ?: 'records_without_source';
}

public function queueMakeSearchable(Collection $models): void
{
    $models->loadMissing('source')
        ->groupBy(fn($model) => $model->searchableAs())
        ->each(fn($group) => $this->parentQueueMakeSearchable($group));
}	
tykus's avatar

@Gregoire I am wondering how you decide which index to use whenever you search for results?

Gregoire's avatar

@tykus I am not sure to understand your question.

I simply do something like this depending on which $index I am looking for.

$index = 'source_name_index';
Record::search($searchInput)->within($index);

Please or to participate in this conversation.