PHPackages                             mozex/laravel-searchable - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. mozex/laravel-searchable

ActiveLibrary[Database &amp; ORM](/categories/database)

mozex/laravel-searchable
========================

Advanced multi-column, relation, and morph search for Laravel Eloquent models

1.0.2(1mo ago)2658↓100%MITPHPPHP ^8.2.0CI passing

Since Apr 16Pushed 1w agoCompare

[ Source](https://github.com/mozex/laravel-searchable)[ Packagist](https://packagist.org/packages/mozex/laravel-searchable)[ Docs](https://github.com/mozex/laravel-searchable)[ GitHub Sponsors](https://github.com/mozex)[ RSS](/packages/mozex-laravel-searchable/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (14)Versions (4)Used By (0)

Laravel Searchable
==================

[](#laravel-searchable)

[![Latest Version on Packagist](https://camo.githubusercontent.com/859b1d89b94cdc457d0022320ffca454379946386135e533d0d52e479159e8b0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d6f7a65782f6c61726176656c2d73656172636861626c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mozex/laravel-searchable)[![GitHub Checks Action Status](https://camo.githubusercontent.com/92a356d2f22ed48bd39f65b94c3a8983f7c426ee24022ff6ffb3c7bc900df168/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6d6f7a65782f6c61726176656c2d73656172636861626c652f636865636b732e796d6c3f6272616e63683d6d61696e266c6162656c3d636865636b73267374796c653d666c61742d737175617265)](https://github.com/mozex/laravel-searchable/actions?query=workflow%3AChecks+branch%3Amain)[![Docs](https://camo.githubusercontent.com/6bae6cab4895bc9b3768db4e7f44e2640da7b03da9635f43f34a1ea306f23e0e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646f63732d6d6f7a65782e6465762d3130423938313f7374796c653d666c61742d737175617265)](https://mozex.dev/docs/laravel-searchable/v1)[![License](https://camo.githubusercontent.com/86ccab3dc837c7210e0fcc912480cc1bbba18c4857f71005b6011ee6136e7b60/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d6f7a65782f6c61726176656c2d73656172636861626c653f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mozex/laravel-searchable)[![Total Downloads](https://camo.githubusercontent.com/aaaf6395f2dc27b49fb2706d343ac1fc7909e17eb45174aed8d33582eaf3474f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d6f7a65782f6c61726176656c2d73656172636861626c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mozex/laravel-searchable)

Add a `Searchable` trait to any Eloquent model and search across multiple columns, regular relations, polymorphic relations, and even cross-database relations with a single `->search()` call. Works alongside Laravel Scout. Ships with optional Filament integration for table search and global search.

> **[Read the full documentation at mozex.dev](https://mozex.dev/docs/laravel-searchable/v1)**: searchable docs, version requirements, detailed changelog, and more.

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Search Types](#search-types)
    - [Direct Columns](#direct-columns)
    - [Relation Columns](#relation-columns)
    - [Morph Relations](#morph-relations)
    - [Cross-Database Relations](#cross-database-relations)
- [Case Sensitivity](#case-sensitivity)
- [Column Filtering](#column-filtering)
- [Performance](#performance)
- [Filament Integration](#filament-integration)
    - [Global Search](#global-search)
- [Handling Conflicts](#handling-conflicts)
    - [Laravel Scout](#laravel-scout)
    - [Existing `search` Methods](#existing-search-methods)

Support This Project
--------------------

[](#support-this-project)

I maintain this package along with [several other open-source PHP packages](https://mozex.dev/docs) used by thousands of developers every day.

If my packages save you time or help your business, consider [**sponsoring my work on GitHub Sponsors**](https://github.com/sponsors/mozex). Your support lets me keep these packages updated, respond to issues quickly, and ship new features.

Business sponsors get logo placement in package READMEs. [**See sponsorship tiers →**](https://github.com/sponsors/mozex)

Installation
------------

[](#installation)

> **Requires [PHP 8.2+](https://php.net/releases/)** - see [all version requirements](https://mozex.dev/docs/laravel-searchable/v1/requirements)

```
composer require mozex/laravel-searchable
```

That's it. No config files to publish, no migrations to run.

Basic Usage
-----------

[](#basic-usage)

Add the `Searchable` trait to your model and define which columns should be searchable. You can mix direct columns, relation columns, and morph relations in the same array:

```
use Mozex\Searchable\Searchable;

class Comment extends Model
{
    use Searchable;

    public function searchableColumns(): array
    {
        return [
            'body',                          // direct column
            'author.name',                   // BelongsTo relation
            'tags.name',                     // HasMany relation
            'commentable:post.title',        // morph relation
            'commentable:video.name',        // another morph type
        ];
    }

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

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

Then search:

```
// Shortest form, searches all configured columns
Comment::search('laravel')->get();

// Chain with other query constraints
Comment::query()
    ->where('published', true)
    ->search($request->input('q'))
    ->paginate();
```

The search wraps all its conditions in a `WHERE (... OR ...)` group, so it plays nicely with any existing query constraints.

Search Types
------------

[](#search-types)

Dot is for regular relations (`author.name`), colon is for morph relations where you have to name the target type because the package can't infer it (`commentable:post.title`).

### Direct Columns

[](#direct-columns)

Plain column names on the model's own table:

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

### Relation Columns

[](#relation-columns)

Use dot notation to search through BelongsTo and HasMany relations:

```
public function searchableColumns(): array
{
    return [
        'title',
        'author.name',      // BelongsTo
        'author.email',     // BelongsTo, different column
        'comments.body',    // HasMany
        'tags.name',        // BelongsToMany / HasMany
    ];
}
```

### Morph Relations

[](#morph-relations)

For polymorphic relations, use `relation:morphType.column` notation. The morph type needs to match your morph map alias:

```
// In a ServiceProvider:
Relation::morphMap([
    'post' => Post::class,
    'video' => Video::class,
]);
```

```
class Comment extends Model
{
    use Searchable;

    public function searchableColumns(): array
    {
        return [
            'body',
            'commentable:post.title',        // search Post's title
            'commentable:video.name',         // search Video's name
            'commentable:post.author.name',   // nested: Post -> Author -> name
        ];
    }

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

Nested relations inside morph targets work too. `commentable:post.author.name` first resolves the morph to a Post, then follows the `author` relation on Post to search the author's name.

### Cross-Database Relations

[](#cross-database-relations)

If a BelongsTo relation points to a model on a different database connection, the package picks this up on its own. Since cross-database JOINs aren't possible, it runs a separate query on the external connection, fetches matching IDs (capped at 50 by default), and uses `whereIn` on the foreign key.

Morph relations to external connections work the same way.

The cap keeps the resulting `IN (...)` clause from getting unmanageably large when a search term hits a lot of rows on the external side. If 50 isn't the right number for your data, pass `externalLimit`:

```
Post::search('term', externalLimit: 200)->get();
```

The same parameter works on `applySearch()` and on the Filament `advancedSearchable()` macro.

Case Sensitivity
----------------

[](#case-sensitivity)

Searches are case-insensitive by default. `Comment::search('LARAVEL')` matches rows containing `laravel`, `Laravel`, or `LARAVEL` without any extra flag or argument.

This comes from Laravel's `whereLike` helper, which the package uses for every search type (direct, relation, morph, and external). Actual behavior follows your database:

- **MySQL / MariaDB**: case-insensitive on the usual `_ci` collations like `utf8mb4_unicode_ci` and `utf8mb4_0900_ai_ci`. A column on a `_bin` or `_cs` collation matches case-sensitively.
- **PostgreSQL**: always case-insensitive. `whereLike` compiles to `ILIKE`.
- **SQLite**: case-insensitive for ASCII only. `Café` and `café` won't match each other under the default `LIKE`.

The package doesn't expose a flag to flip this. If you need case-sensitive matching on a normally case-insensitive column, change the column's collation at the database level.

Column Filtering
----------------

[](#column-filtering)

You can override or adjust which columns are searched per-query:

```
// Search only specific columns (ignores searchableColumns)
Post::search('term', in: ['title', 'body'])->get();

// Add extra columns on top of searchableColumns
Post::search('term', include: ['slug'])->get();

// Exclude specific columns from searchableColumns
Post::search('term', except: ['author.name'])->get();

// Combine them
Post::search('term', include: ['slug'], except: ['body'])->get();
```

All three parameters accept a string or an array.

Performance
-----------

[](#performance)

This package compiles to `LIKE '%term%'`. The leading wildcard defeats B-tree indexes, so every row in the searched column gets scanned, and each relation or morph column adds a correlated `EXISTS` subquery on top. On small-to-mid tables this is fine; into the millions of rows, or once a search hits many relations, switch to Laravel Scout with Meilisearch, Typesense, or Algolia. Indexing the searched columns themselves won't help, but indexing the columns you also filter on (e.g., `tenant_id`, `status`) lets the database prune rows before the `LIKE` runs. On Postgres, a `pg_trgm` GIN index is the one thing that genuinely speeds up `LIKE '%term%'` while staying in SQL.

Filament Integration
--------------------

[](#filament-integration)

When Filament is installed, the package registers an `advancedSearchable()` macro on `TextColumn`. Add it to one column in your table, and it'll search across all your model's configured searchable columns:

```
use Filament\Tables\Columns\TextColumn;

TextColumn::make('title')
    ->advancedSearchable()
    ->sortable(),
```

You can pass the same `in`, `include`, `except` parameters:

```
TextColumn::make('title')
    ->advancedSearchable(except: ['author.name'])
    ->sortable(),
```

### Global Search

[](#global-search)

Register the provider on your panel:

```
use Mozex\Searchable\Filament\SearchableGlobalSearchProvider;

return $panel
    ->id('admin')
    ->path('admin')
    ->globalSearch(SearchableGlobalSearchProvider::class);
```

Then on each resource, define `getGloballySearchableAttributes()` to control which columns global search uses for that resource. Return all of the model's columns, or a subset:

```
class CourseResource extends Resource
{
    // Use everything the model declared as searchable
    public static function getGloballySearchableAttributes(): array
    {
        return (new Course)->searchableColumns();
    }
}

class PostResource extends Resource
{
    // Or limit global search to a subset, even though
    // the Post model has more columns in searchableColumns()
    public static function getGloballySearchableAttributes(): array
    {
        return ['title', 'author.name'];
    }
}
```

Each resource you want in global search needs to define `getGloballySearchableAttributes()`. Resources without it are excluded from global search entirely.

Resources whose models don't use the `Searchable` trait fall through to Filament's default global search behavior.

Handling Conflicts
------------------

[](#handling-conflicts)

### Laravel Scout

[](#laravel-scout)

Scout and this package both expose a `search()` method on your model. Scout's is a static method that hits its search engine; this package's is a query scope that runs SQL. Technically, different call paths, so they don't collide.

In practice, having two `search` entry points on the same model gets confusing fast. The cleaner approach is to alias this package's scope to a different name using PHP's trait aliasing, so each search path has its own clear name:

```
use Laravel\Scout\Searchable;
use Mozex\Searchable\Searchable as DatabaseSearchable;

class Lesson extends Model
{
    use DatabaseSearchable {
        scopeSearch as scopeDatabaseSearch;
    }
    use Searchable;

    public function searchableColumns(): array
    {
        return ['name', 'description'];
    }
}
```

Now `Lesson::search('term')` runs Scout's full-text search, and `Lesson::databaseSearch('term')` runs this package's database search. No ambiguity.

For the Filament macro, pass the renamed method:

```
TextColumn::make('name')->advancedSearchable(method: 'databaseSearch')
```

### Existing `search` Methods

[](#existing-search-methods)

Sometimes you can't reach this package's scope through `$query->search()` because something else already owns that name. Two common cases:

- **A custom Eloquent Builder defines its own `search()`** (Corcel's `PostBuilder` is the textbook example). `$query->search()` calls the Builder's method, not this package's scope.
- **A parent model you extend already declares `scopeSearch`** with a different signature. Adding our trait causes a fatal error because PHP enforces method signature compatibility between trait methods and inherited methods.

For both cases, use `applySearch()` to invoke the scope directly without going through the `search` name:

```
$query = Product::query();
$query->getModel()->applySearch($query, 'term');
$results = $query->get();
```

`applySearch` accepts the same parameters as the scope:

```
$query->getModel()->applySearch($query, 'term', in: ['title', 'body']);
$query->getModel()->applySearch($query, 'term', except: ['author.name']);
```

If a parent model's `scopeSearch` signature conflicts with this package's, alias our scope to a different name when adding the trait (the same pattern as the Scout case above):

```
use Mozex\Searchable\Searchable as DatabaseSearchable;

class Product extends VendorModel
{
    use DatabaseSearchable {
        scopeSearch as scopeDatabaseSearch;
    }
}
```

For the Builder case specifically, you can also override the Builder's `search()` to delegate back to `applySearch`, so the rest of your codebase keeps calling `$query->search()`:

```
class ProductBuilder extends \Corcel\Model\Builder\PostBuilder
{
    public function search($term = false, ...$args): self
    {
        $query = Product::query();

        (new Product)->applySearch($query, $term, ...$args);

        return $query;
    }
}
```

Resources
---------

[](#resources)

Visit the [documentation site](https://mozex.dev/docs/laravel-searchable/v1) for searchable docs auto-updated from this repository.

- **[AI Integration](https://mozex.dev/docs/laravel-searchable/v1/ai-integration)**: Use this package with AI coding assistants via Context7 and Laravel Boost
- **[Requirements](https://mozex.dev/docs/laravel-searchable/v1/requirements)**: PHP, Laravel, and dependency versions
- **[Changelog](https://mozex.dev/docs/laravel-searchable/v1/changelog)**: Release history with linked pull requests and diffs
- **[Contributing](https://mozex.dev/docs/laravel-searchable/v1/contributing)**: Development setup, code quality, and PR guidelines
- **[Questions &amp; Issues](https://mozex.dev/docs/laravel-searchable/v1/questions-and-issues)**: Bug reports, feature requests, and help
- **[Security](mailto:hello@mozex.dev)**: Report vulnerabilities directly via email

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance94

Actively maintained with recent releases

Popularity22

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

3

Last Release

52d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4d33a9cb8263eab6fa251e57fc763c5c6977701e5cafa0edc538c4651bb9780?d=identicon)[mozex](/maintainers/mozex)

---

Top Contributors

[![mozex](https://avatars.githubusercontent.com/u/18025667?v=4)](https://github.com/mozex "mozex (52 commits)")

---

Tags

database-searcheloquentfilamentglobal-searchlaravelmorph-searchmulti-column-searchphprelation-searchsearchsearchablesearchlaraveleloquentsearchablemulti-columnfilamentmozexrelation\_searchmorph-search

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/mozex-laravel-searchable/health.svg)

```
[![Health](https://phpackages.com/badges/mozex-laravel-searchable/health.svg)](https://phpackages.com/packages/mozex-laravel-searchable)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[spatie/laravel-health

Monitor the health of a Laravel application

88011.3M149](/packages/spatie-laravel-health)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k29.9M42](/packages/kirschbaum-development-eloquent-power-joins)[watson/validating

Eloquent model validating trait.

9743.4M53](/packages/watson-validating)[clickbar/laravel-magellan

This package provides functionality for working with the postgis extension in Laravel.

436834.4k1](/packages/clickbar-laravel-magellan)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
