Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Searchable Laravel Package

maize-tech/laravel-searchable

Add the Searchable trait to any Eloquent model and run unified searches across one or many of them. Supports wildcards, result limits, model filtering and Scout integration, returning structured results ready for list cards and search pages.

View on GitHub
Deep Wiki
Context7

Getting Started

maize-tech/laravel-searchable adds a lightweight, database-level search to Eloquent models — no Scout, no external engine. It compiles a LIKE-based query across the columns you declare, weighted so you can order by relevance.

Install and first search (≈5 minutes):

composer require maize-tech/laravel-searchable

The service provider is auto-discovered. Publish the config only if you need to change defaults (accuracy/terminals/wildcard behaviour):

php artisan vendor:publish --tag=searchable-config

Then opt a model in and declare its searchable columns with weights:

use Illuminate\Database\Eloquent\Model;
use Maize\Searchable\HasSearch;

class User extends Model
{
    use HasSearch;

    public function getSearchableAttributes(): array
    {
        return [
            'name'     => 5,
            'email'    => 3,
            'username' => 2,
            'bio'      => 1,
        ];
    }
}

First use case — a controller/endpoint:

Route::get('/users', fn (Request $request) => User::query()
    ->search($request->string('q'))
    ->searchableRanking()
    ->paginate(25));

Where to look first: getSearchableAttributes() is the whole contract — that array is your search configuration. Everything else (accuracy, wildcards, ranking column) is tuning around it.

4.2.0 note: this release adds Laravel 13 support. It is not a breaking change, but it does move the supported ceiling — check that composer.json constraints and your PHP version line up before upgrading a Laravel 13 app. The rest of the diff is GitHub Actions bumps (git-auto-commit-action, laravel-pint-action, fetch-metadata); nothing in src/ or config changes, so no code migration is required.

Implementation Patterns

Let the query builder do the work. search() returns an Eloquent builder, so compose it with your normal constraints rather than filtering in PHP:

$users = User::query()
    ->where('tenant_id', $tenantId)
    ->where('is_active', true)
    ->search($term)
    ->searchableRanking()
    ->paginate();

This is the pattern to reach for in 90% of cases — authorization/tenant scoping stays in the builder, search is just another where clause.

Targeted searches with an array. Pass a map to search only specific columns (values become the terms for those keys), which is useful for advanced-search UIs:

Post::query()->search([
    'title'   => $request->input('title'),
    'content' => $request->input('body'),
])->get();

Weights are for ordering, not filtering. Weights (5/3/2/1) feed the ranking computation only. A row matching only bio still appears — it just sinks. Tune weights to reflect what users actually intend when they type a term (titles/names high, long text bodies low).

Wrap it in a reusable scope. Don't repeat the same search()->searchableRanking() chain across controllers:

public function scopeSearchFor(Builder $query, ?string $term): Builder
{
    return filled($term)
        ? $query->search($term)->searchableRanking()
        : $query->latest();
}

This is also the natural place to centralise the empty-term guard (see Gotchas).

Form Requests + pagination. Validate/normalise the term in a Form Request (trim, minimum length, strip control chars) and always paginate. An unpaginated %term% scan on a large table is the number one production incident with this package.

Accuracy tuning. The package's accuracy setting (config, and typically overridable per query) controls how strict terminal matching is — broader accuracy finds more, precise accuracy returns fewer, more literal hits. Set a sane global default in config/searchable.php and override it per query for endpoints where users expect exact-ish matching (e.g. SKU lookup) versus exploratory search.

Custom accessors / computed fields. If a column needs transformation before searching, keep the transformation in SQL (or store a denormalised search column) rather than post-filtering the collection — the whole value of the package is that filtering and pagination happen in the database.

Testing. Assert on the builder/result set, not on generated SQL strings. Use factories to create rows that differ only in one searchable column so a failing weight or an accidentally-missing column is obvious.

Gotchas and Tips

  • % and _ in user input. These are LIKE wildcards. A user typing % will match every row. Escape the term before it reaches search() unless you deliberately want wildcard support.
  • Leading wildcards defeat indexes. %term% cannot use a B-tree index. Fine up to tens/hundreds of thousands of rows; beyond that, move to MySQL full-text / Postgres tsvector / Scout. Treat this package as the pragmatic first search, not the permanent one.
  • Case sensitivity differs per driver. MySQL's default collation is case-insensitive, SQLite's LIKE is case-insensitive for ASCII, Postgres LIKE is case-sensitive. A test suite on SQLite can pass while production on Postgres misses results. Normalise (e.g. LOWER() comparisons or ILIKE) if your app is driver-agnostic.
  • Empty string behaviour. search('') may degrade to LIKE '%%' and return everything, or error depending on the driver. Guard with filled($term) in a scope — always.
  • Weights do nothing without the ranking step. If results look unsorted, you probably omitted searchableRanking() (or your orderBy is overriding it — ranking must be applied last, and be aware of it interacting with orderBy clauses you already added).
  • Config caching. After editing config/searchable.php, run php artisan config:clear (and remember config:cache in deploy). Silent "my config change did nothing" bugs are almost always this.
  • SQL Server / non-standard drivers can be stricter about LOWER() on text/ntext columns — cast the column if you hit type errors.
  • Debugging: dump the compiled query with ->search($term)->toSql() plus getBindings(), or register a DB::listen() in a Tinker session. That reveals immediately whether the term was escaped, which columns were included, and whether wildcards landed where you expected.
  • Extension points: the trait's getSearchableAttributes() can be made dynamic (per-role, per-tenant column sets) — just be aware it is called during query building, so keep it cheap and side-effect free. Publish the config to change accuracy/terminals globally; prefer per-query overrides for one-off endpoints so you don't shift behaviour for every model at once.
  • Upgrade path: on 4.1.x → 4.2.0 there is nothing to do beyond composer. The Laravel 13 support is additive; if you're pinned to Laravel 13, this is the version to be on.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource