As a Laravel developer, I’ve always appreciated the elegance of Eloquent. But when it came to filtering complex queries especially when the filters depended on request parameters I found myself writing bloated controller logic or messy conditional clauses that just didn’t feel very “Laravel-ish.”
So I built QueryFilter a lightweight Laravel package designed to help you cleanly and fluently apply filters to your Eloquent queries, without sacrificing readability or flexibility.
Let me tell you the story behind it.
🧩 The Problem: Conditional Queries Get Messy Fast
If you’ve worked on real-world APIs, you’ve seen something like this:
$query = User::query();
if (request('name')) {
$query->where('name', 'like', '%' . request('name') . '%');
}
if (request('email')) {
$query->where('email', request('email'));
}
if (request('active')) {
$query->where('is_active', true);
}
This starts out manageable but grows into a rat’s nest as your filters expand. Moving this logic to repositories or service classes helps only so much.
I wanted a way to:
- Decouple request parameters from query logic
- Make filters reusable and testable
- Keep controller code clean and focused
💡 The Solution: A Modular Query Filtering Approach
Inspired by Laravel’s pipeline pattern and modular thinking, I built QueryFilter to let you define each filter as its own class, like so:
namespace App\Filters;
use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;
class Search extends Filter
{
/**
* Search results using whereLike
*
* @param Builder $builder
* @return Builder
*/
protected function applyFilter(Builder $builder): Builder
{
// Search with relationship: ['name', 'posts.title']
return $builder->whereLike(['name'], $this->getValue());
}
}
Then apply them like this:
User::queryFilter([
Name::class,
Email::class,
Active::class,
])->get();
This approach achieves single responsibility, high readability, and full testability.
⚙️ Under the Hood
QueryFilter uses a pipeline to pass the query builder through each filter class. Each filter decides whether or not to act based on the request data (or any source you inject). You can even mock them in your tests or reuse them in multiple models.
By default, it works out of the box with Laravel’s container and request lifecycle — no complex setup needed.
✅ Use Cases
- REST API filtering: Keep controllers clean while supporting many filters
- Admin panels: Modular filters tied to inputs like search forms or toggles
- Reusable filters: Create a library of filter classes to apply across models
🧪 Testing and Maintenance
Because each filter is just a class with logic, testing is trivial:
public function test_name_filter_applies_like_query()
{
$query = User::query();
$filter = new Name(['name' => 'John']);
$filter->handle($query, fn($q) => $q);
$this->assertStringContainsString('like', $query->toSql());
}
This also means you can refactor or extend individual filters without affecting the rest of the query pipeline.
📦 Open Source and Ready to Use
👉 Contributions and feedback are welcome
👋 Final Thoughts
I didn’t build QueryFilter because Laravel lacked power — I built it to make my daily workflow cleaner and more expressive. As senior developers, we care not just about what works but how it works — and more importantly, how easy it is to maintain and scale.
If you’ve ever been frustrated by sprawling query logic, I hope this package brings the same relief it brought me.
Let me know what you think or how you’d improve it.