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

h-farm/laravel-searchable

Laravel Searchable by h-farm adds simple, driver-based full-text search to Eloquent models, letting you index records and run clean, reusable search queries with minimal setup in Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install the release that matches your framework. composer require maize-tech/laravel-searchable — 4.2.0 is the first line that declares Laravel 13 support, so L13 apps must be on ^4.2. Apps on L10–L12 can stay on 4.1.0 with zero code changes; upgrading is a no-op (see Gotchas).
  • No migrations, no service provider work, no published config required to start. This package is a query-builder convenience layer over your existing tables — it does not create an index. That is the single most important thing to understand before you adopt it.
  • Where to look first, in this order:
    1. The trait source (src/) — it is small; read it once and you will know exactly what SQL it generates (spoiler: LIKE/ILIKE conditions, not full-text search).
    2. The package config file, if you publish it (php artisan vendor:publish --tag="searchable-config" where available) — the matching/case-sensitivity knob lives there.
    3. The model you intend to make searchable — the trait plus one method declaring the columns is the whole integration.
  • Minimal first use case: add the Searchable trait to User, declare the searchable columns (name, email, and any relation you want reachable, e.g. posts.title), then build a single controller action that takes ?q=, validates it, and returns User::search($q)->paginate(). Ship that behind a simple search box before touching multi-model search.
  • Method naming check: the trait's searchable-columns method has been documented under slightly different names across versions. Open the README at the tag you install and match it exactly — it is the only piece of the API you can plausibly get wrong.

Implementation Patterns

  • Treat it as a query scope, not a search engine. Compose it with the rest of the builder: authorization scoping first, then search, then ordering — User::where('team_id', $team)->search($q)->orderBy('name')->paginate(20). Order of chaining matters for readability and for what you can later replace.
  • Short-circuit empty queries. An empty q should return an unfiltered (or empty) result set rather than a LIKE '%%' full-table scan. Put this in the FormRequest or a SearchService, not in every controller.
  • Wrap it in one application-level service. App\Search\SearchService with a search(string $term, array $models) method means swapping in Scout/Meilisearch/laravel-scout later is a one-file change and not a grep-and-replace across fifty controllers. Do this on day one even though it feels like ceremony.
  • Multi-model search. For a global search box, loop over a whitelist of searchable models, collect LengthAwarePaginator results or a flattened SearchResult DTO (type, title, url, excerpt), and merge. Cap per-model results (e.g. 5) and link to a per-model search page for the full list — otherwise you get wildly unbalanced result mixes.
  • Relation columns need eager loading. If you display relation-derived context (posts.title) in the results list, add ->with('posts'); the package's join/subquery does not hydrate relations for you.
  • Beware row multiplication on relation search. If the generated SQL joins relation tables, a parent with three matching children appears three times. Guard with ->distinct() or group by the parent key, and verify with ->toSql() before shipping.
  • Relevance ordering is yours to define. The package does not give you a score. If you need "best match first", either order by a column you control (e.g. exact-prefix match first via a CASE you add yourself), or accept deterministic alphabetical/chronological ordering and be explicit about it in the UI.
  • Feature-test the search endpoint, not the package. Write test_search_finds_user_by_partial_name style tests against your own route. Those tests survive a future move to Scout; tests of the trait's internals do not.
  • Pair it with a dedicated FormRequest + API Resource. SearchRequest validating q (string, max 100) and page; SearchResultResource shaping the payload. Keeps the escaping/trimming logic in one place.

Gotchas and Tips

  • This is LIKE-based search, not full-text search. No stemming, no relevance ranking, no typo tolerance, and a leading % defeats your index. Fine for admin panels and datasets up to a few hundred thousand rows; not fine as a public "search our catalogue" feature. Decide this before you build the UI, not after.
  • Case sensitivity is a database concern, not a package concern. MySQL's default _ci collation makes LIKE case-insensitive; PostgreSQL LIKE is case-sensitive (ILIKE or LOWER() needed). This is the #1 source of "works on my machine, broken in prod" reports with this kind of package — confirm the generated SQL on your actual driver.
  • Escape user input for % and _. A user searching for 100% or foo_bar will otherwise get wildcard behaviour. Trim and escape in the FormRequest before it reaches the query.
  • Add your own indexes. The package adds none. Index the columns you declare as searchable; a leading-wildcard LIKE still can't use them for the search itself, but every other filter/order in the same query benefits.
  • Don't expose sensitive columns to search. Marking email or national_id searchable lets users enumerate records by probing partial values. Only declare columns that are already visible in results, or scope hard by tenant/policy first.
  • Tenant/policy scoping must come before search() in the chain. There is no built-in global scope; search does not respect your BelongsToTenant trait beyond whatever the underlying builder already applies.
  • Testing on SQLite in-memory behaves differently from MySQL/Postgres for LIKE case-sensitivity and collation. If behaviour matters, run the search tests against the real driver in CI or assert case-insensitively.
  • Debugging: dump ->toSql() and ->getBindings() on the builder before ->get(); or watch the queries via Telescope/Debugbar. Nine times out of ten the surprising result is a wildcard escaping issue or an unexpected JOIN.
  • Laravel 13 upgrade note for 4.2.0: the only functional change is a widened framework constraint. No breaking changes, no new API, no deprecations. The rest of the release is Dependabot bumps to GitHub Actions — irrelevant to consumers. If composer require maize-tech/laravel-searchable:^4.2 fails on an L13 app, the conflict is almost certainly another dependency still capping illuminate/* below 13; run composer why-not rather than assuming this package is at fault. Also check the PHP floor Laravel 13 imposes on your runtime before bumping.
  • Extension points: override the searchable-columns method per model to vary behaviour (different columns for different models, computed/aliased search), override/replace the matching operator via config for ILIKE/LOWER() behaviour, and wrap the trait behind your own service (above) so the escape hatch is cheap. There is no event/hook surface worth relying on — the extension story is "write your own query and keep the same public interface".
  • Pin the major (^4.2) and let Renovate/Dependabot handle minors. This package's minors have historically been framework-support and CI hygiene, so upgrade risk is low, but the searchable-columns method name is the one thing to re-read in the changelog on every bump.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource