Skip to main content

Laravel Scout Search Configuration

Configuration guide for Laravel Scout search, including searching joined columns from related tables.

Basic Configuration

use Laravel\Scout\Searchable;

class Product extends Model
{
    use Searchable;

    public function toSearchableArray(): array
    {
        return [
            'name' => $this->name,
            'description' => $this->description,
            'sku' => $this->sku,
        ];
    }
}
Note: With the database Scout driver, no indexing happens by default. Only include actual database columns in toSearchableArray(), not computed values unless they’re real columns (like computed_text_id).

Searching Joined Columns

For Search AND Display

Override loadDomainObject() when you need the joined data for both search and display purposes:
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Inly\Core\Models\Traits\HasDomainObject;
use Laravel\Scout\Attributes\SearchUsingPrefix;
use Laravel\Scout\Searchable;

class Account extends Model
{
    use HasDomainObject, Searchable;

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    #[SearchUsingPrefix(['computed_text_id'])]
    public function toSearchableArray(): array
    {
        return [
            'computed_text_id' => $this->computed_text_id,
            'users.name' => $this->user?->name,
            'users.ssn' => $this->user?->ssn,
        ];
    }

    /**
     * Override to eager load relationships and join tables.
     * Automatically called during Scout searches and domain object queries.
     */
    #[Scope]
    public function loadDomainObject(Builder $query): Builder
    {
        return $query->with(['campaign'])
            ->joinRelationship('user')
            ->select(
                'accounts.*',
                'users.name',
                'users.ssn',
            );
    }
}
Note: Use joinRelationship() to automatically join related tables. Use table-prefixed keys (e.g., 'users.name') in toSearchableArray() when referencing joined columns.

For Search ONLY

Override searchQueryConstraints() when you only need the joined data for search, not for display:
#[Scope]
public function searchQueryConstraints(Builder $query): Builder
{
    return $query->loadDomainObject()
        ->joinRelationship('tag')
        ->select(
            'products.*',
            'tags.name',
        );
}

#[Scope]
public function loadDomainObject(Builder $query): Builder
{
    return $query->with(['category', 'images']); // No join needed for display
}
This keeps the join overhead out of non-search queries.

Search Behavior

Default: LIKE Matching

By default, all fields in toSearchableArray() use WHERE LIKE %search% OR ... (contains matching). Use #[SearchUsingPrefix] for fields that should match from the start (e.g., “123” matches “12345” but not “51234”):
#[SearchUsingPrefix(['computed_text_id', 'order_number'])]
public function toSearchableArray(): array
{
    return [
        'computed_text_id' => $this->computed_text_id,
        'order_number' => $this->order_number,
        'customer_name' => $this->customer_name,
    ];
}
Important: Numeric IDs cannot use prefix search. You must create a computed text column to enable prefix searching on IDs.
For better performance and advanced matching (tokenization, stemming, stop words) on long text columns, add a full-text index:
// Migration
$table->fullText('description');
$table->fullText(['title', 'description']); // Multi-column index
Warning: Don’t use full-text indexes on short single-value fields (names, emails, SKUs) - they won’t match well. Full-text is for longer content where tokenization helps (descriptions, articles, comments).
// Migration
$table->computedTextColumn('id'); // Creates computed_text_id

// Model
#[SearchUsingPrefix(['computed_text_id'])]
public function toSearchableArray(): array
{
    return [
        'computed_text_id' => $this->computed_text_id,
    ];
}

Using Power Joins

The joinRelationship() method comes from the eloquent-power-joins package.

Common Patterns

// Basic join
$query->joinRelationship('user')

// Nested relationships (join through multiple tables)
$query->joinRelationship('account.user')

// Multiple relationships
$query->joinRelationship('user')
    ->joinRelationship('campaign')
Note: See the Power Joins documentation for more options.

Best Practices

  1. Choose the right method:
    • Use loadDomainObject() for joins needed in search AND display
    • Use searchQueryConstraints() for joins only needed in search
  2. Use joinRelationship(): Prefer ->joinRelationship('user') over manual joins for cleaner code
  3. Choose appropriate join type: Use leftJoinRelationship() when the relationship might not exist
  4. Always select base columns first: ->select('table_name.*', 'joined.column', ...)
  5. Use table-prefixed keys: In toSearchableArray(), use 'users.name' when referencing joined columns
  6. Use safe navigation: $this->user?->name instead of $this->user->name in toSearchableArray()
  7. Index joined columns: Ensure foreign keys and joined columns have database indexes