netbull/awesome-filter-bundle
Netbull AwesomeFilterBundle is a Symfony bundle that adds reusable filter types for building flexible, query-driven filter forms. Simplify filtering logic in your admin panels and listings with configurable, ready-to-use filters that plug into your existing Symfony stack.
Know what you're installing first. netbull/awesome-filter-bundle is a Symfony bundle, not a Laravel package. There is no AwesomeFilterServiceProvider, no facade, no config/awesome-filter.php. Everything below assumes you are either (a) pulling framework-agnostic value classes out of it, or (b) writing a ~50-line bridge yourself.
composer require netbull/awesome-filter-bundle:^7.0
Current release is v7.0.1 (2025-06-12) — a patch that only clears a deprecation. Nothing in it changes the integration story below, but the version number itself is informative: this is the seventh major line of a package with zero dependents. Plan accordingly (see Gotchas).
Where to look, in order:
src/ — the filter/value classes you'll actually touch. Read these before anything else; with 0 stars and 0 dependents, the source is the documentation.DependencyInjection/Configuration.php — the config tree is the single best inventory of every knob the bundle exposes. If a feature isn't a node here, it isn't configurable.Resources/config/services.* — shows which classes are registered as services and, critically, which constructor dependencies they demand (this is where Doctrine or Symfony Form coupling will bite you).composer.json — check for symfony/form, doctrine/orm, symfony/http-foundation. Any of those present means the package is not extractable in isolation without pulling a large chunk of the Symfony stack along. Also check the php and symfony/* version constraints: a v7 line almost certainly means PHP 8.2+ and Symfony 6/7 only.README / CHANGELOG — last, not first. With a project this small the changelog is a release log, not a usage guide.First use case to target: a filterable index endpoint. GET /api/orders?status=paid&created_from=2025-01-01&sort=-total. The bundle's value proposition is representing that querystring as typed filter objects instead of a $request->only([...]) soup in the controller. Build exactly that one endpoint before considering a second.
If composer.json requires symfony/framework-bundle, stop and reconsider. You'll be installing a mini Symfony kernel inside Laravel to filter a query. In that case the honest recommendation is to read the package as design inspiration (how it models filters, how it maps request data to filter DTOs) and reimplement the ~200 lines that matter as app/Filters/. That is not a knock on the code — it's a dependency-graph reality.
If the core classes are framework-agnostic (plain PHP, no ContainerInterface in constructors), you're in luck: composer require, autoload works, and you skip the bundle registration entirely.
The workable integration is a two-layer split. The bundle supplies (or inspires) the filter object; you own the translation to Eloquent.
// app/Filters/OrderFilter.php — a typed, validated representation of the querystring
final class OrderFilter
{
public function __construct(
public readonly ?string $status = null,
public readonly ?string $createdFrom = null,
public readonly string $sort = '-created_at',
) {}
public static function fromRequest(Request $request): self
{
$data = $request->validate([
'status' => ['nullable', 'string', 'in:pending,paid,shipped'],
'created_from' => ['nullable', 'date'],
'sort' => ['nullable', 'string', 'in:created_at,-created_at,total,-total'],
]);
return new self(
status: $data['status'] ?? null,
createdFrom: $data['created_from'] ?? null,
sort: $data['sort'] ?? '-created_at',
);
}
}
Then a single applier that owns all query mutation:
final class OrderFilterApplier
{
public function apply(Builder $query, OrderFilter $filter): Builder
{
$query->when($filter->status, fn ($q, $v) => $q->where('status', $v));
$query->when($filter->createdFrom, fn ($q, $v) => $q->where('created_at', '>=', $v));
[$col, $dir] = str_starts_with($filter->sort, '-')
? [substr($filter->sort, 1), 'desc']
: [$filter->sort, 'asc'];
return $query->orderBy($col, $dir);
}
}
If the bundle gives you a FilterInterface or an apply()/getQueryBuilder() contract, implement that contract in OrderFilterApplier so you get the bundle's composition features (chaining, collections) for free while Eloquent does the actual SQL. Check this contract against v7 specifically — after six prior majors it has very likely been renamed or re-signatured; don't code against signatures from a two-year-old blog post or the v2-era README.
The pattern these bundles exist to serve: an array of independent filter objects, each conditionally contributing a WHERE, so a 14-column admin table doesn't become a 90-line controller method.
$filters = collect([$statusFilter, $dateRangeFilter, $searchFilter])
->filter(fn ($f) => $f->isActive()); // skip unset filters entirely
$filters->each(fn ($f) => $f->applyTo($query));
The isActive() guard is the important bit — it prevents where('status', null) from silently producing status IS NULL and returning an empty grid at 2am.
Only if the bundle genuinely requires it. Register the bundle's services manually in a service provider rather than booting a Symfony kernel:
public function register(): void
{
$this->app->singleton(SomeBundleFilterFactory::class, function () {
return new SomeBundleFilterFactory(/* hand-wire constructor deps */);
});
}
Read Resources/config/services.* to get the constructor argument list right — that file is the ground truth, not the README. On a v7 line, expect autowiring aliases and PHP 8 attributes there rather than the explicit <argument> XML/YAML lists you'd see in v1-era bundles; the shape of that file changes across majors.
it('filters by status', function () {
Order::factory()->create(['status' => 'paid']);
Order::factory()->create(['status' => 'pending']);
$filter = new OrderFilter(status: 'paid');
$results = (new OrderFilterApplier)->apply(Order::query(), $filter)->get();
expect($results)->toHaveCount(1);
});
Assert on the filter's behaviour, so if you later swap the bundle for a hand-rolled implementation the tests survive. This matters more than usual here: with seven majors behind it, the upstream API you're wrapping is demonstrably not stable, but your tests can be.
Route::get('/orders', function (Request $request, OrderFilterApplier $applier) {
$filter = OrderFilter::fromRequest($request);
return OrderResource::collection($applier->apply(Order::query(), $filter)->paginate());
});
One line in the route file. The moment filter logic leaks into controllers it duplicates, and with a package this small you have no upstream to fix the duplication for you.
AGPL-3.0 is the headline risk. This is a network-copyleft licence. If you deploy a Laravel app that links this package and expose it over a network, the AGPL's §13 obliges you to offer corresponding source to users. For most commercial SaaS this is a non-starter without legal sign-off. If you only want the ideas, read the MIT/BSD-licensed equivalents or write your own. Treat this as a blocking question, not a footnote. Re-verify the licence in composer.json for v7 — licences do change between majors, and this is the one fact worth confirming with your own eyes rather than inheriting from any assessment.
v7.0.1 tells you more than the changelog does. The release itself is trivial — one commit, "clear deprecation" — but the version is the signal. A package at major 7 with 0 dependents has churned its public API six times without an installed base to slow it down. That is the opposite of the stability guarantee a Laravel dev normally reads into a high major number (where ^7.0 on a popular package means "battle-tested and settled"). Here, treat the major as a churn counter, not a maturity badge. Pin exactly and read every diff.
The deprecation fix implies a modern, maintained stack — and a moving floor. Clearing deprecations almost always means aligning with a newer PHP or Symfony minor. Concretely: v7.0.1 likely targets PHP 8.2/8.3+ and Symfony 6.4/7.x, and the previous majors are effectively abandoned (the author will not be backporting that fix). Two consequences for you: (a) if your Laravel app is on an older PHP, v7 may not install at all — check composer.json require.php before you plan the work; (b) you cannot "just stay on v6" to dodge a problem, because v6 is now a dead branch. Budget for keeping pace, or don't take the dependency.
Zero stars, zero dependents, zero community. There will be no StackOverflow answers, no blog posts, no third-party tutorials — and now, no answers for v7 specifically, since any stray content you find will describe v1–v3 APIs. Every question is answered by reading src/. Budget for that. Correspondingly, the code is likely small enough to read in an afternoon — which is the saving grace.
Symfony bundle ≠ Laravel package. No auto-discovery, no php artisan vendor:publish (no publishable assets unless Resources/config ships them, and even then the config format is Symfony YAML/XSD, not Laravel's array config). config/packages/*.yaml examples in the docs are inert in a Laravel app.
Doctrine leakage. If src/ contains Doctrine\ORM\QueryBuilder typehints, you cannot reuse the filtering logic against Eloquent without an adapter. Check composer.json before writing a single line of integration code.
Symfony Form dependency. Some of these filter bundles build filters as Symfony Forms. If so, you inherit the entire Form component's validation/rendering model — which duplicates and fights Laravel's Validator and FormRequest. At that point the package is fighting your framework, and you should stop.
Release cadence vs. stability. Active maintenance is confirmed (a patch landed 2025-06-12, same day as the v7.0.0 tag — the author shipped and immediately cleaned up). That's a good sign for responsiveness and a bad sign for your calendar: majors have historically arrived fast enough that the deprecation fix was needed within hours of the major. Pin explicitly (composer require netbull/awesome-filter-bundle:^7.0), commit composer.lock, and read the diff before composer update. Never run a bare composer update in a project that depends on this.
Debugging: if a filter silently does nothing, the failure is almost always (a) the filter's isActive() returning false on a value you thought was set, or (b) the applier not being invoked because you called ->get() before ->apply(). Log the composed query with ->toSql() / ->dd() at the applier boundary — that single log line will resolve 80% of issues. On v7, add a third suspect: an API shape that changed since the docs you're reading, so confirm method names against src/ rather than memory.
Extension points to look for: a filter interface you can implement for custom types, a registry/collection you can push into from a service provider, and a config node for default behaviour. If none of those three exist, the package is a closed set of filter types — fine for reading, bad for extending.
Escape hatch, and my actual recommendation for most teams: if the integration cost exceeds an afternoon, lift the Filter DTO pattern into app/Filters/, delete the dependency, and sidestep AGPL entirely. You keep the architectural benefit, the package's design insight, and none of the licence, bundle-shimming, or major-version-churn risk. A one-commit patch release doesn't change that calculus — if anything, the fact that the package is on major 7 reinforces it.
How can I help you explore Laravel packages today?