Laravel Packages

Search Polymorphic Relations and Filament Tables With Laravel Searchable

Published
Search Polymorphic Relations and Filament Tables With Laravel Searchable image

Laravel Searchable, by Mozex, adds a model trait for searching local columns, related records, and polymorphic relations through one Eloquent scope. Its typed morph syntax and Filament relevance sorting make it worth a look for admin panels where a search needs to reach beyond the model's own table.

This is a separate package from Spatie's Laravel Searchable. Spatie's package combines results from multiple models and custom sources. Mozex's package filters an Eloquent query, so you can keep chaining constraints, eager loading, and pagination.

Search Model Columns

Add the Searchable trait to a model and return the columns you want to search from searchableColumns():

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Mozex\Searchable\Searchable;

class Post extends Model
{
    use Searchable;

    public function searchableColumns(): array
    {
        return ['title', 'body'];
    }
}

You can then search both columns through an Eloquent query:

use App\Models\Post;

$posts = Post::search('laravel')->paginate(20);

This returns posts containing laravel in either title or body. The array order gives title matches priority over matches found only in the body.

On the Eloquent scope, multi-word searches require every term to match somewhere in the configured columns. Double quotes keep a phrase together. By default, only the first 10 terms are searched; later terms are discarded. User-supplied % and _ characters are treated literally.

Search Polymorphic Relations

Suppose a moderation screen lists comments on both posts and videos. You want to find a comment by its body, the post's title, or the video's name. Each target model has different searchable fields.

First, register the morph aliases in a service provider's boot() method:

use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
    'post' => Post::class,
    'video' => Video::class,
]);

Then declare the paths on your comment model:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Mozex\Searchable\Searchable;

class Comment extends Model
{
    use Searchable;

    public function searchableColumns(): array
    {
        return [
            'body',
            'commentable:post.title',
            'commentable:video.name',
        ];
    }

    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}

The colon identifies the morph type. You can also follow relations inside a target with a path such as commentable:post.author.name, assuming Post defines an author relation. Ordinary relations use dot notation, including nested paths such as author.company.name.

With the columns configured, search through the normal query builder:

use App\Models\Comment;

$comments = Comment::query()
    ->where('approved', false)
    ->search('laravel')
    ->with('commentable')
    ->paginate(20);

The package groups its search conditions, preserving the approval constraint. Laravel already provides whereHasMorph() for writing this query yourself; the package lets you declare the searchable paths once and reuse them across searches.

Search Relations on Another Connection

A direct BelongsTo relation can point to a model on a separate database connection. For example, a local post might belong to an author stored in a shared user database.

Declare author.name as a searchable column and the package detects the connection difference. It queries the related model separately, then filters the posts using the returned IDs. Supported morph targets can use this approach too.

External lookups return at most 50 matching IDs per column per term by default. You can change that for a query:

Post::search('Jane', externalLimit: 200)->paginate(20);

This cap can exclude otherwise valid results when more authors match than the limit allows. Increasing it means more IDs in the local query. The separate-connection shortcut applies to direct BelongsTo relations and supported morph targets; it does not extend to arbitrary nested relation paths across connections.

In version 1.3.0, external lookups use the related model's primary key and do not apply extra where clauses defined on the relationship method. A BelongsTo relation using a different owner key, or relying on those additional constraints, needs a custom query instead.

Rank Filament Search Results

For a model using the trait, the package's advancedSearchable() macro lets a Filament text column search all the model's configured paths:

use Filament\Tables\Columns\TextColumn;

TextColumn::make('body')
    ->advancedSearchable()
    ->sortable();

Filament already supports searching relationships. The extra here is reusing the model's search definition, including typed morph paths, alongside automatic relevance ordering.

The order of searchableColumns() sets column priority. Within a directly searchable column, exact matches rank above prefixes, followed by substrings. Single-hop relation and morph columns also receive graded scores; deeper paths and external lookups receive a matched-or-unmatched score.

During a table search, relevance takes priority and the table's default sort breaks ties. An explicit column sort selected by the user takes precedence. Automatic ranking applies to tables whose models use the trait and can be disabled with searchable.filament.relevance_sort.

For panel-wide search, the package also provides SearchableGlobalSearchProvider. Register it on your panel and use each resource's getGloballySearchableAttributes() to choose which columns to search. Normal Filament resource access and result URL requirements still apply. Results are ranked within each resource's category.

Choosing a Search Package

Relation search and relevance ranking are available in other packages. For example, Protone Media's Cross Eloquent Search supports nested relationships, native full-text search on MySQL and PostgreSQL, and pagination across combined results from multiple model types. That combined result set suits a search page returning posts and videos together. Mozex's package suits a comment query that searches fields on its related posts and videos.

There is also a difference in relation ranking: Cross Eloquent Search's documentation states that its relevance ordering does not support relationship searches. Mozex's package grades single-hop relation matches and gives deeper paths a matched-or-unmatched score.

It also runs without a separate search index, though Scout's database engine offers that option too, with prefix and database full-text search strategies.

Mozex's package builds substring searches using LIKE '%term%'. Leading wildcards generally prevent ordinary B-tree indexes from accelerating text matching, and relation searches and ranking add subqueries. Test it against your data and query patterns before adopting it for a busy search endpoint. If you need typo tolerance or an external search index, consider Laravel Scout with Meilisearch.

The strongest reason to try this package is a relation-heavy admin search that would otherwise need custom morph queries or separate-connection lookups, particularly when the same model also appears in Filament tables.

Installation

As of this writing, version 1.3.0 requires PHP 8.2+ and Laravel ^12.69 or ^13.30. The optional Filament integration declares support for Filament 4 and 5.

composer require mozex/laravel-searchable

There are no package migrations. To publish config/searchable.php and adjust defaults such as the term limit or external lookup cap, run php artisan searchable:install.

You can find the source and usage examples in the Mozex Laravel Searchable repository. The package is released under the MIT licence.

Yannick Lyn Fatt photo

Staff Writer at Laravel News and Full stack web developer.

Sponsored

laravelcloud logo
Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Cloud

The latest

View all →
Laravel Rest Api 3.0: Typed Fields, Metrics, and OpenAPI Docs image

Laravel Rest Api 3.0: Typed Fields, Metrics, and OpenAPI Docs

Read article
Real-Time Inertia Props With Laravel Reverb image

Real-Time Inertia Props With Laravel Reverb

Read article
PhpStorm 2026.3 Adds an Agent Workspace and PHP 8.6 Support image

PhpStorm 2026.3 Adds an Agent Workspace and PHP 8.6 Support

Read article
Insert Middleware Before or After Existing Laravel Middleware image

Insert Middleware Before or After Existing Laravel Middleware

Read article
Laravel Herd 2: Coding Agents, Workspaces, and Cloud Deploys image

Laravel Herd 2: Coding Agents, Workspaces, and Cloud Deploys

Read article
Strict Request Input Casting in Laravel image

Strict Request Input Casting in Laravel

Read article