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

hnhdigital-os/laravel-search-components

A Laravel package of reusable search components that adds filtering, sorting, and query handling to your models and Blade views, making it quick to build searchable listings without repeating boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

This package gives you a persisted, component-driven search layer: a Search record owns a set of components (fields, operators, values), and a trait on your Eloquent models turns those components into a query. It is a saved-search/query-builder abstraction, not a full-text engine — think "user-defined filters stored in the DB and re-runnable."

Minimal setup:

composer require hnhdigital-os/laravel-search-components
php artisan vendor:publish --provider="HnHDigital\LaravelSearchComponents\ServiceProvider"
php artisan migrate

Where to look first, in this order:

  1. config/search.php — model bindings and table names. Everything is swappable here; check it before assuming class names.
  2. src/Models/Search.php and the component model — this is the schema you are actually persisting.
  3. The Searchable trait (applied to the model you want to search) — this is the contract you implement.
  4. An existing test in tests/ if shipped — fastest way to see a working component array.

First use case: add the trait to one model, define two or three searchable fields, create a Search with a couple of components, and dump the resulting query with ->toSql() before you wire any UI.

use HnHDigital\LaravelSearchComponents\Traits\Searchable;

class Order extends Model
{
    use Searchable;

    protected $searchable = ['reference', 'status', 'created_at'];
}

$search = Search::create(['name' => 'Overdue orders', 'user_id' => auth()->id()]);
$search->components()->createMany([
    ['field' => 'status',     'operator' => '=',  'value' => 'pending'],
    ['field' => 'created_at', 'operator' => '<',  'value' => now()->subDays(30)],
]);

$results = Order::search($search)->paginate(25);

Verify the method name (search, applySearch, scope vs. static) against the trait source — this package has changed its public surface across majors and the README lags.

Implementation Patterns

Persist, don't inline. The value of the package is that a search is a row, not a controller method. Treat Search as an aggregate: name, owner, components, and optionally a is_public/shared flag. That gives you saved searches, shareable filters, and scheduled "email me these results" for free.

Two-layer creation. Create searches from a form request that validates components as an array (field in the allowed set, operator in the allowed set, value present), then map to component rows in a service or action class. Never let the raw request reach the trait — an unvalidated field is a SQL injection or at minimum an Unknown column 500.

class StoreSearchRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name'                 => ['required', 'string', 'max:120'],
            'components'           => ['required', 'array', 'min:1'],
            'components.*.field'   => ['required', Rule::in(Order::searchableFields())],
            'components.*.operator'=> ['required', Rule::in(['=', '!=', '>', '<', '>=', '<=', 'like', 'in'])],
            'components.*.value'   => ['required'],
        ];
    }
}

Whitelist per model, not globally. Keep the $searchable array next to the model. It doubles as your validation source and your UI field list, so the form and the query builder can't drift apart.

Compose with existing scopes. Apply the package's filter on top of your normal authorization and soft-delete scopes, not the other way round:

Order::query()
    ->where('tenant_id', $tenant->id)   // tenancy first
    ->search($search)                   // components second
    ->with('customer')                  // eager loads last
    ->paginate(25);

Clone before reusing. If the builder returned mutates state (many query-builder packages do), clone it when you need both a count() and the paginated result set.

API surface. Return components as a resource with field/operator/value and let the frontend rebuild the same payload it sent. Round-tripping the same shape in and out keeps the UI trivial and makes "edit saved search" a PATCH.

Testing. Build searches with factory() or direct create(), then assert on ->toSql() for the generated shape and on the result set for behaviour. Test the empty-component case explicitly — it's the one that silently returns everything.

Gotchas and Tips

3.0.2 is a patch with an opaque changelog. The release notes are a bare compare link with no described changes. Pin 3.0.1 if you need certainty and diff the two tags manually before upgrading production; patch releases on this package have historically included trait and namespace touches that can affect overridden classes.

Config cache trap. Class names are read from config. If you swap the Search model to your own subclass and forget php artisan config:cache (or forget to clear it after deploying), you'll get "class not found" or, worse, the package's base model writing to a table your app doesn't have. Put config:clear in the deploy script.

Swap models by extending, not editing. The supported extension point is your own model extending the package's, bound in config/search.php. Never edit published migrations in place once they've run in a shared environment — publish again into a new migration.

Empty component set = no filtering. Unless the trait guards it, a Search with zero components returns the whole table. Guard in the trait wrapper or throw before executing, and never treat an empty search as "show everything" in a UI with pagination — someone will page through 400k rows.

Operator strings are not validated by the package. They are concatenated into SQL. Anything user-supplied that isn't in a strict allow-list is your vulnerability, not the package's.

like is your problem. LIKE '%term%' cannot use an index. For reference or name fields on large tables, either switch to a =/prefix operator or pair the component with a dedicated index/LIKE 'term%'.

Debugging generated queries. DB::listen() or ->dd() on the builder is more reliable than reading the trait; the component loop makes the SQL hard to predict from the input. Log toSql() plus getBindings() when a filter "doesn't work" — it's usually a component that silently matched nothing.

Date and cast handling. Values stored in the component table come back as strings. A Carbon instance you assign may or may not survive the round-trip depending on casts in the package model. Cast explicitly and test a date operator end to end.

Trait collisions. Both this trait and common packages (Searchable from Laravel Scout, HasSearch style traits elsewhere) may define conflicting method names. If Scout is installed, alias one trait (use Searchable { ... as ... }) — Searchable is an overloaded name and a silent collision here produces confusing method resolution, not an error.

Polymorphic scope. If you store a searchable_type per search, keep FQCNs stable. Renaming a model class (or moving it to App\Models\) orphans saved searches with no error until someone runs them.

Extension points worth knowing. Config model bindings, the component model's casts/attributes, and the trait's field accessor are the three places to override. Anything deeper means a fork — wrap the trait behind a small service in your own app so a future major version doesn't ripple through your controllers.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox