{"id":51091476,"url":"https://github.com/mozex/laravel-searchable","last_synced_at":"2026-06-24T02:32:31.547Z","repository":{"id":351836548,"uuid":"1212481876","full_name":"mozex/laravel-searchable","owner":"mozex","description":"Advanced multi-column, relation, and morph search for Laravel Eloquent models","archived":false,"fork":false,"pushed_at":"2026-06-23T11:38:40.000Z","size":158,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T13:33:30.058Z","etag":null,"topics":["database-search","eloquent","filament","global-search","laravel","morph-search","multi-column-search","php","relation-search","search","searchable"],"latest_commit_sha":null,"homepage":"https://mozex.dev/docs/laravel-searchable","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mozex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"mozex"}},"created_at":"2026-04-16T12:26:24.000Z","updated_at":"2026-06-23T11:38:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mozex/laravel-searchable","commit_stats":null,"previous_names":["mozex/laravel-searchable"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/mozex/laravel-searchable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozex%2Flaravel-searchable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozex%2Flaravel-searchable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozex%2Flaravel-searchable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozex%2Flaravel-searchable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mozex","download_url":"https://codeload.github.com/mozex/laravel-searchable/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozex%2Flaravel-searchable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34714992,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database-search","eloquent","filament","global-search","laravel","morph-search","multi-column-search","php","relation-search","search","searchable"],"created_at":"2026-06-24T02:32:31.000Z","updated_at":"2026-06-24T02:32:31.537Z","avatar_url":"https://github.com/mozex.png","language":"PHP","funding_links":["https://github.com/sponsors/mozex"],"categories":[],"sub_categories":[],"readme":"# Laravel Searchable\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/mozex/laravel-searchable.svg?style=flat-square)](https://packagist.org/packages/mozex/laravel-searchable)\n[![GitHub Checks Action Status](https://img.shields.io/github/actions/workflow/status/mozex/laravel-searchable/checks.yml?branch=main\u0026label=checks\u0026style=flat-square)](https://github.com/mozex/laravel-searchable/actions?query=workflow%3AChecks+branch%3Amain)\n[![Docs](https://img.shields.io/badge/docs-mozex.dev-10B981?style=flat-square)](https://mozex.dev/docs/laravel-searchable/v1)\n[![License](https://img.shields.io/packagist/l/mozex/laravel-searchable?style=flat-square)](https://packagist.org/packages/mozex/laravel-searchable)\n[![Total Downloads](https://img.shields.io/packagist/dt/mozex/laravel-searchable.svg?style=flat-square)](https://packagist.org/packages/mozex/laravel-searchable)\n\nAdd a `Searchable` trait to any Eloquent model and search across multiple columns, regular relations, polymorphic relations, and even cross-database relations with a single `-\u003esearch()` call. Works alongside Laravel Scout. Ships with optional Filament integration for table search and global search.\n\n\u003e **[Read the full documentation at mozex.dev](https://mozex.dev/docs/laravel-searchable/v1)**: searchable docs, version requirements, detailed changelog, and more.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Basic Usage](#basic-usage)\n- [Search Types](#search-types)\n  - [Direct Columns](#direct-columns)\n  - [Relation Columns](#relation-columns)\n  - [Morph Relations](#morph-relations)\n  - [Cross-Database Relations](#cross-database-relations)\n- [Case Sensitivity](#case-sensitivity)\n- [Column Filtering](#column-filtering)\n- [Performance](#performance)\n- [Filament Integration](#filament-integration)\n  - [Global Search](#global-search)\n- [Handling Conflicts](#handling-conflicts)\n  - [Laravel Scout](#laravel-scout)\n  - [Existing `search` Methods](#existing-search-methods)\n\n## Support This Project\n\nI maintain this package along with [several other open-source PHP packages](https://mozex.dev/docs) used by thousands of developers every day.\n\nIf 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.\n\nBusiness sponsors get logo placement in package READMEs. [**See sponsorship tiers →**](https://github.com/sponsors/mozex)\n\n## Installation\n\n\u003e **Requires [PHP 8.2+](https://php.net/releases/)** - see [all version requirements](https://mozex.dev/docs/laravel-searchable/v1/requirements)\n\n```bash\ncomposer require mozex/laravel-searchable\n```\n\nThat's it. No config files to publish, no migrations to run.\n\n## Basic Usage\n\nAdd 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:\n\n```php\nuse Mozex\\Searchable\\Searchable;\n\nclass Comment extends Model\n{\n    use Searchable;\n\n    public function searchableColumns(): array\n    {\n        return [\n            'body',                          // direct column\n            'author.name',                   // BelongsTo relation\n            'tags.name',                     // HasMany relation\n            'commentable:post.title',        // morph relation\n            'commentable:video.name',        // another morph type\n        ];\n    }\n\n    public function author(): BelongsTo\n    {\n        return $this-\u003ebelongsTo(User::class);\n    }\n\n    public function commentable(): MorphTo\n    {\n        return $this-\u003emorphTo();\n    }\n}\n```\n\nThen search:\n\n```php\n// Shortest form, searches all configured columns\nComment::search('laravel')-\u003eget();\n\n// Chain with other query constraints\nComment::query()\n    -\u003ewhere('published', true)\n    -\u003esearch($request-\u003einput('q'))\n    -\u003epaginate();\n```\n\nThe search wraps all its conditions in a `WHERE (... OR ...)` group, so it plays nicely with any existing query constraints.\n\n## Search Types\n\nDot 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`).\n\n### Direct Columns\n\nPlain column names on the model's own table:\n\n```php\npublic function searchableColumns(): array\n{\n    return ['title', 'body', 'slug'];\n}\n```\n\n### Relation Columns\n\nUse dot notation to search through BelongsTo and HasMany relations:\n\n```php\npublic function searchableColumns(): array\n{\n    return [\n        'title',\n        'author.name',      // BelongsTo\n        'author.email',     // BelongsTo, different column\n        'comments.body',    // HasMany\n        'tags.name',        // BelongsToMany / HasMany\n    ];\n}\n```\n\n### Morph Relations\n\nFor polymorphic relations, use `relation:morphType.column` notation. The morph type needs to match your morph map alias:\n\n```php\n// In a ServiceProvider:\nRelation::morphMap([\n    'post' =\u003e Post::class,\n    'video' =\u003e Video::class,\n]);\n```\n\n```php\nclass Comment extends Model\n{\n    use Searchable;\n\n    public function searchableColumns(): array\n    {\n        return [\n            'body',\n            'commentable:post.title',        // search Post's title\n            'commentable:video.name',         // search Video's name\n            'commentable:post.author.name',   // nested: Post -\u003e Author -\u003e name\n        ];\n    }\n\n    public function commentable(): MorphTo\n    {\n        return $this-\u003emorphTo();\n    }\n}\n```\n\nNested 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.\n\n### Cross-Database Relations\n\nIf 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.\n\nMorph relations to external connections work the same way.\n\nThe 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`:\n\n```php\nPost::search('term', externalLimit: 200)-\u003eget();\n```\n\nThe same parameter works on `applySearch()` and on the Filament `advancedSearchable()` macro.\n\n## Case Sensitivity\n\nSearches are case-insensitive by default. `Comment::search('LARAVEL')` matches rows containing `laravel`, `Laravel`, or `LARAVEL` without any extra flag or argument.\n\nThis comes from Laravel's `whereLike` helper, which the package uses for every search type (direct, relation, morph, and external). Actual behavior follows your database:\n\n- **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.\n- **PostgreSQL**: always case-insensitive. `whereLike` compiles to `ILIKE`.\n- **SQLite**: case-insensitive for ASCII only. `Café` and `café` won't match each other under the default `LIKE`.\n\nThe 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.\n\n## Column Filtering\n\nYou can override or adjust which columns are searched per-query:\n\n```php\n// Search only specific columns (ignores searchableColumns)\nPost::search('term', in: ['title', 'body'])-\u003eget();\n\n// Add extra columns on top of searchableColumns\nPost::search('term', include: ['slug'])-\u003eget();\n\n// Exclude specific columns from searchableColumns\nPost::search('term', except: ['author.name'])-\u003eget();\n\n// Combine them\nPost::search('term', include: ['slug'], except: ['body'])-\u003eget();\n```\n\nAll three parameters accept a string or an array.\n\n## Performance\n\nThis 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.\n\n## Filament Integration\n\nWhen 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:\n\n```php\nuse Filament\\Tables\\Columns\\TextColumn;\n\nTextColumn::make('title')\n    -\u003eadvancedSearchable()\n    -\u003esortable(),\n```\n\nYou can pass the same `in`, `include`, `except` parameters:\n\n```php\nTextColumn::make('title')\n    -\u003eadvancedSearchable(except: ['author.name'])\n    -\u003esortable(),\n```\n\n### Global Search\n\nRegister the provider on your panel:\n\n```php\nuse Mozex\\Searchable\\Filament\\SearchableGlobalSearchProvider;\n\nreturn $panel\n    -\u003eid('admin')\n    -\u003epath('admin')\n    -\u003eglobalSearch(SearchableGlobalSearchProvider::class);\n```\n\nThen 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:\n\n```php\nclass CourseResource extends Resource\n{\n    // Use everything the model declared as searchable\n    public static function getGloballySearchableAttributes(): array\n    {\n        return (new Course)-\u003esearchableColumns();\n    }\n}\n\nclass PostResource extends Resource\n{\n    // Or limit global search to a subset, even though\n    // the Post model has more columns in searchableColumns()\n    public static function getGloballySearchableAttributes(): array\n    {\n        return ['title', 'author.name'];\n    }\n}\n```\n\nEach resource you want in global search needs to define `getGloballySearchableAttributes()`. Resources without it are excluded from global search entirely.\n\nResources whose models don't use the `Searchable` trait fall through to Filament's default global search behavior.\n\n## Handling Conflicts\n\n### Laravel Scout\n\nScout 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.\n\nIn 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:\n\n```php\nuse Laravel\\Scout\\Searchable;\nuse Mozex\\Searchable\\Searchable as DatabaseSearchable;\n\nclass Lesson extends Model\n{\n    use DatabaseSearchable {\n        scopeSearch as scopeDatabaseSearch;\n    }\n    use Searchable;\n\n    public function searchableColumns(): array\n    {\n        return ['name', 'description'];\n    }\n}\n```\n\nNow `Lesson::search('term')` runs Scout's full-text search, and `Lesson::databaseSearch('term')` runs this package's database search. No ambiguity.\n\nFor the Filament macro, pass the renamed method:\n\n```php\nTextColumn::make('name')-\u003eadvancedSearchable(method: 'databaseSearch')\n```\n\n### Existing `search` Methods\n\nSometimes you can't reach this package's scope through `$query-\u003esearch()` because something else already owns that name. Two common cases:\n\n- **A custom Eloquent Builder defines its own `search()`** (Corcel's `PostBuilder` is the textbook example). `$query-\u003esearch()` calls the Builder's method, not this package's scope.\n- **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.\n\nFor both cases, use `applySearch()` to invoke the scope directly without going through the `search` name:\n\n```php\n$query = Product::query();\n$query-\u003egetModel()-\u003eapplySearch($query, 'term');\n$results = $query-\u003eget();\n```\n\n`applySearch` accepts the same parameters as the scope:\n\n```php\n$query-\u003egetModel()-\u003eapplySearch($query, 'term', in: ['title', 'body']);\n$query-\u003egetModel()-\u003eapplySearch($query, 'term', except: ['author.name']);\n```\n\nIf 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):\n\n```php\nuse Mozex\\Searchable\\Searchable as DatabaseSearchable;\n\nclass Product extends VendorModel\n{\n    use DatabaseSearchable {\n        scopeSearch as scopeDatabaseSearch;\n    }\n}\n```\n\nFor 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-\u003esearch()`:\n\n```php\nclass ProductBuilder extends \\Corcel\\Model\\Builder\\PostBuilder\n{\n    public function search($term = false, ...$args): self\n    {\n        $query = Product::query();\n\n        (new Product)-\u003eapplySearch($query, $term, ...$args);\n\n        return $query;\n    }\n}\n```\n\n## Resources\n\nVisit the [documentation site](https://mozex.dev/docs/laravel-searchable/v1) for searchable docs auto-updated from this repository.\n\n- **[AI Integration](https://mozex.dev/docs/laravel-searchable/v1/ai-integration)**: Use this package with AI coding assistants via Context7 and Laravel Boost\n- **[Requirements](https://mozex.dev/docs/laravel-searchable/v1/requirements)**: PHP, Laravel, and dependency versions\n- **[Changelog](https://mozex.dev/docs/laravel-searchable/v1/changelog)**: Release history with linked pull requests and diffs\n- **[Contributing](https://mozex.dev/docs/laravel-searchable/v1/contributing)**: Development setup, code quality, and PR guidelines\n- **[Questions \u0026 Issues](https://mozex.dev/docs/laravel-searchable/v1/questions-and-issues)**: Bug reports, feature requests, and help\n- **[Security](mailto:hello@mozex.dev)**: Report vulnerabilities directly via email\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmozex%2Flaravel-searchable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmozex%2Flaravel-searchable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmozex%2Flaravel-searchable/lists"}