Download the PHP package mozex/laravel-searchable without Composer

On this page you can find all versions of the php package mozex/laravel-searchable. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-searchable

Laravel Searchable

Latest Version on Packagist GitHub Checks Action Status Docs License Total Downloads

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: searchable docs, version requirements, detailed changelog, and more.

Table of Contents

Support This Project

I maintain this package along with several other open-source PHP packages used by thousands of developers every day.

If my packages save you time or help your business, consider sponsoring my work on GitHub Sponsors. 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 →

Installation

Requires PHP 8.2+ - see all version requirements

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

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:

Then search:

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

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

Plain column names on the model's own table:

Relation Columns

Use dot notation to search through BelongsTo and HasMany relations:

The path can be as deep as you need. The last segment is the column, everything before it is the relation chain:

Two-hop and deeper paths get a matched-or-not relevance score rather than the exact/prefix/substring grading. See Relevance Ordering.

Morph Relations

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

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.

You need the morph type. A MorphTo written in plain dot notation (commentable.title, no type) can't resolve to a single model, so the package skips that column rather than guessing. Always name the type with the colon syntax.

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:

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

Multi-Word Search

Type two words and both have to match. They don't need to be in the same column, and they don't need to be in that order.

Each word becomes its own OR group across your searchable columns, and the groups are joined with AND. So every word has to turn up somewhere, but each one is free to turn up in a different place.

Wrap something in double quotes to keep it together as one term:

Splitting stops at 10 words. Everything past that is dropped, so a pasted paragraph can't turn into a hundred-predicate query. Raise or lower it with maxTerms, or set it to 1 to go back to matching the whole string as one literal phrase:

A search that's only whitespace is treated as no search at all, the same as null or ''.

Upgrading from 1.1.x: multi-word searches return more rows than they used to. Before, search('jane doe') looked for the literal string jane doe; now it looks for jane and doe separately. Single-word searches are unaffected. Pass maxTerms: 1 to keep the old behavior.

Case Sensitivity

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

The package lowercases both sides of the comparison rather than leaning on the column's collation, so this holds even on binary-collated columns. That matters for the JSON columns translatable models use, which MySQL compares case-sensitively under a plain LIKE. Actual behavior follows your database:

There's no flag to turn this off. If you need a case-sensitive match, build that constraint yourself alongside the search.

Wildcards in Search Terms

% and _ are LIKE wildcards, and the package escapes both before they reach the query. A user typing _ into your search box gets rows containing a literal underscore, not every row in the table. Someone searching 100% gets the products actually called "100% Cotton", not everything starting with 100.

Nothing to configure. It applies to the filter and to the relevance ranking alike, so a term with a % in it still ranks exact matches above substring matches.

Column Filtering

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

All three parameters accept a string or an array.

Relevance Ordering

Results come back ranked by how well they match, not by id. The order of your searchableColumns() array sets the priority: a hit in the first column outranks a hit that only shows up in a later one.

Say a Course searches ['title', 'description']. Search "laravel", and a course with "Laravel" in its title sorts above one that only mentions it in the description, even when the description course was created first. Title is column 0, so it wins. This is the whole point. Before, both matched equally and you had to scroll to find the one you meant.

Within a single column, closer matches rank higher. An exact value beats a prefix, which beats a buried substring. Searching "laravel" against three titles:

The same grading runs on relation and morph columns. author.name is scored right after title, commentable:post.title in its own array position, and so on down the list. A HasMany relation is scored by its best-matching child, so an author with a post titled exactly "Laravel" sorts above one with "A Laravel Tutorial".

Cross-database relations, multi-hop relations like author.company.name, and two-hop morph columns like commentable:post.author.name get a simpler matched-or-not score instead of the exact/prefix/substring grading, because those can't be ranked inside a single SQL statement. Column priority still holds for them.

Ranking multi-word searches

With more than one word, a column scores each word separately and adds them up, then a match on the whole phrase outranks any combination of the individual words. Search "laravel guide" against two titles:

Both rows come back either way. The one that reads the way you typed it goes on top.

For the matched-or-not column types listed above, the score is 1 only when that column contains every word, and 0 otherwise.

Ordering is on by default. Turn it off with orderByRelevance: false:

The same parameter works on applySearch().

Add your own orderBy() before search() and yours stays the primary sort, with relevance as the tiebreaker:

Call orderBy() after search() and the roles flip: relevance leads, your column breaks ties.

In Filament tables this is automatic while searching. See Ranked Table Search.

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.

Relevance ordering adds a scoring expression to the ORDER BY for each searchable column, and for relation and morph columns that's another correlated subquery. It only runs over rows that already passed the WHERE, so the cost tracks the number of matches, not the table size. If you don't need ranked results, orderByRelevance: false skips all of it.

What multi-word search costs

A one-word search generates the same query it always did: one OR group, one EXISTS per relation column, one scoring subquery per column in the ORDER BY. Nothing about it got slower.

Each extra word adds another OR group to the WHERE, so a three-word search does roughly three times the filtering work of a one-word search. There's no way around that; requiring all three words means testing all three. The ORDER BY is the part that doesn't grow. However many words you type, each column still contributes exactly one scoring expression and at most one subquery. The extra words become inline arithmetic on rows that already passed the WHERE.

Cross-database columns run one query on the other connection per word. On a direct ->search() those results are shared between the filter and the ranking, so a one-word search makes a single round trip where it used to make two. Filament tables are the exception: they build the filter and the ranking in separate passes, so each pass does its own lookup, exactly as before.

The maxTerms cap is the backstop. It stops a pasted paragraph in a public search box from generating an unbounded query.

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:

You can pass the same in, include, except, externalLimit, and maxTerms parameters:

Ranked Table Search

Tables rank by relevance automatically while searching. There's nothing to add to your tables. When the package boots with Filament present, it registers a global table query scope, so any table whose model uses the Searchable trait floats the best matches to the top the moment someone types in the search box, using the same column-priority and exact/prefix/substring rules from Relevance Ordering.

It's deliberately careful about not stepping on your existing sorts:

This works because Filament's search and sort are separate phases. The macro can't rank on its own (Filament runs the search callback inside a nested WHERE, and Eloquent throws away any orderBy added there), so the ranking rides on a query scope that runs before sorting instead.

If you'd rather wire ranking yourself, turn the automatic behavior off once, anywhere in a service provider:

Then apply it where you want, for example inside a modifyQueryUsing or a custom sort, reusing the same decision logic:

Global Search

Global search ranks results on its own. The provider applies relevance ordering for you, so the most relevant hits land at the top of each resource's results with no extra wiring.

Register the provider on your panel:

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:

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

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:

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:

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:

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

applySearch accepts the same parameters as the scope:

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):

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():

Resources

Visit the documentation site for searchable docs auto-updated from this repository.

License

The MIT License (MIT). Please see License File for more information.


All versions of laravel-searchable with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2.0
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/database Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0
spatie/laravel-package-tools Version ^1.16
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package mozex/laravel-searchable contains the following files

Loading the files please wait ...