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 Search Laravel Package

mmanos/laravel-search

A driver-based full-text search package for Laravel that lets you index and search Eloquent models with a simple, fluent API. Supports multiple backends and integrates cleanly into existing models and queries.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require mmanos/laravel-search

The package is now at v1.0.5. That patch only touches the Elasticsearch driver — it does not widen framework support. The composer.json still constrains Laravel to the 5.x line, so run composer why-not mmanos/laravel-search before assuming it installs anywhere newer.

Laravel 5.4 and below: register the provider and facade manually in config/app.php.

Mmanos\Search\SearchServiceProvider::class,
// aliases
'Search' => Mmanos\Search\Facade::class,

Laravel 5.5 auto-discovers them.

Publish and inspect the config first

php artisan vendor:publish --provider="Mmanos\Search\SearchServiceProvider"

This drops config/search.php. Start here: it defines the default driver and per-driver connection settings. The MySQL driver is the one you'll almost certainly use — it queries your existing tables with MATCH ... AGAINST, so there is no separate index to keep in sync.

Create the fulltext index

The package does not create indexes for you. Add one in a migration (or raw SQL):

DB::statement('ALTER TABLE posts ADD FULLTEXT search_index (title, body)');

Use $table->fullText([...]) in newer Laravel migrations if you prefer, but remember SQLite has no fulltext support, so tests must run against MySQL/Postgres.

Make a model searchable

use Mmanos\Search\SearchTrait;

class Post extends Eloquent
{
    use SearchTrait;

    protected $searchable = [
        'columns' => [
            'title' => 10,
            'body'  => 5,
        ],
        'relations' => [
            'author' => [
                'columns' => ['name' => 5],
            ],
        ],
    ];
}

Weights are relative, not absolute — a title hit is worth twice a body hit above. Then:

Post::search('laravel eloquent')->get();
Post::search('laravel')->paginate(15);

That is the first use case worth proving out: search a model, paginate results, confirm relevance ordering is sane. Everything else is a variation on it.

Implementation Patterns

Chain query constraints on the search builder. The result object behaves like an Eloquent query for filtering, so keep the scope logic where it belongs:

Post::search($q)
    ->where('status', 'published')
    ->where('created_at', '>', now()->subYear())
    ->orderBy('relevance', 'desc')
    ->with('author', 'tags')
    ->paginate(20);

Eager loading works because the package resolves matches to model IDs and then hydrates via Eloquent — use it aggressively, otherwise you'll N+1 on the very relations you searched through.

Search across relations deliberately. Relation columns are traversed with joins, which means every relation you add to $searchable['relations'] is another join on every query. Keep relations shallow (one level), and only include them when the relation genuinely carries searchable text.

Tune weights against real data, not intuition. Pick 10–20 representative queries, log the top 5 results, adjust the weight map, repeat. The score is a weighted sum of per-column relevance, so a single fat weight (e.g. title => 50) will swamp everything else. Keep the ratio between your heaviest and lightest column under ~10x.

Layer caching at the application level. Results for a query string plus filter set are highly cacheable:

$ids = Cache::remember("search:{$hash}", 300, fn () =>
    Post::search($q)->where('status', 'published')->limit(200)->get()->pluck('id')
);

Cache IDs, not hydrated models, so you can still apply policy checks and fresh eager loads.

Use the index API only if you need a non-SQL backend. The package also exposes Search::index('posts') with insert/update/delete/search against a configured driver. That path requires you to sync manually — wire it into model events (saved, deleted) or a queued job. If you're on MySQL, skip it; the trait path is simpler and has no drift risk.

The Elasticsearch driver is the only part still receiving patches. v1.0.5 fixes the driver's match query handling for the type parameter that Elasticsearch 6 removed. If you are on that path, pin ^1.0.5, and smoke-test one indexed search plus one filtered search after upgrading — older ES clusters (5.x and below) are the risk, since the fix rewrites the query shape rather than branching on server version.

Extension point for custom drivers. Driver classes live under Mmanos\Search\Drivers. Register a custom one in config/search.php and point default at it — useful if you want to wrap Elasticsearch or add query logging without touching package code.

Gotchas and Tips

Version lock is still the biggest risk. The v1.0.5 patch is a driver-level bugfix, not a framework-support release: it does not restore compatibility with Laravel 6+ and will still fail on removed helpers (array_get, str_*), the facade namespace, and the Illuminate\Support\Str refactor. A patch to the ES driver is not a signal the package is maintained for modern Laravel. If you're modernising, budget for a fork or migrate to Laravel Scout with the TNTSearch/Meilisearch driver — Scout is the maintained equivalent of this package's role.

MySQL fulltext defaults will silently drop your queries. innodb_ft_min_token_size defaults to 3 (and stopwords are enabled). Searching "go" or "a/b" returns nothing and looks like a bug in your code. Either change the server variable or pre-process short terms into a LIKE fallback.

Sanitize user input before it reaches AGAINST. The MySQL driver passes the term into boolean-mode fulltext syntax, where +, -, *, ", ~, (, ) are operators. A stray -word becomes an exclusion. Strip or escape these, and validate length — an empty term can produce malformed SQL rather than an empty result set.

Duplicates from relation joins. Searching through a hasMany relation can return the same parent row multiple times unless the query dedupes. If you see repeats, the fix is on your side: collect IDs with distinct() or use ->get()->unique('id') before paginating.

search() is a scope, not the facade. Post::search(...) is the trait's static entry point. Search:: is the package facade for the index API. They don't compose — don't try to pass a facade result into a model scope.

Debug by looking at the generated SQL. The package builds raw SQL, so ->toSql() on the search builder and DB::listen() in a service provider are your primary tools. Most "wrong results" issues are visible immediately: a missing join, an unintended AND, or a weight of 0 on the column you expected to match. On the ES driver you have no SQL — log the request body instead (wrap the driver, or use Search::index()->search() directly in a tinker session) and diff the query DSL against what your ES version accepts.

Soft deletes and global scopes are respected through Eloquent hydration, not through the match step. Verify that the ID-resolution query excludes trashed models on your version; if not, add the constraint explicitly.

Testing. Point your test suite at MySQL (a Docker service or SQLite is not an option for fulltext). Keep a fixture seeder that loads edge cases: short terms, stopwords, punctuation, accented characters — these are where the relevance ranking falls apart first. If you use the ES driver, put a real ES container in CI; a driver bugfix like v1.0.5 is exactly the kind of change a mocked client will not catch.

No built-in highlighting, no typo tolerance, no stemming beyond MySQL's own. If product requirements include those, the package is the wrong tool regardless of version; plan for Scout + Meilisearch instead.

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