Method Illuminate\Database\Query\Builder::filter does not exist
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
# Debugging the Mystery: Why `filter()` Doesn't Exist in Laravel Query Builder
As senior developers working with the Laravel ecosystem, we often encounter situations where custom logic seems perfectly fine conceptually but throws cryptic errors during execution. The error you are facing—`Method Illuminate\Database\Query\Builder::filter does not exist`—stems from a misunderstanding of how Eloquent and the underlying Query Builder methods are chained together.
This post will dissect your code, explain the role of the `$builder` object, and show you the robust, idiomatic Laravel way to implement complex filtering logic for your database queries.
---
## Deconstructing the Error: The Misuse of Methods
The error you encountered, `Method Illuminate\Database\Query\Builder::filter does not exist`, occurs because you are attempting to call a method named `filter()` directly on an Eloquent Query Builder instance (`Thread::latest()`). While Laravel provides powerful methods for building queries (like `where`, `orderBy`, `with`), it does not expose a generic, standalone `filter()` method on the builder itself.
The core issue lies in mixing concerns: you are trying to use a custom filtering mechanism (`$filters`) where standard Eloquent constraints (`where()`) should be applied directly to the query.
Furthermore, the second error you see when changing `latest()` to `all()` highlights another crucial distinction:
`Type error: Argument 1 passed to Illuminate\Support\Collection::filter() must be callable or null, object given...`
This happens because methods like `latest()` return a Query Builder instance. When you try to chain methods that operate on the *results* (like filtering a collection), you are confusing the Query Builder layer with the Collection layer. You cannot apply database constraints directly as if they were standard Collection operations; they must be applied at the database query level first.
## Understanding the `$builder` Object
The concept of `$builder` in your `Filters` abstract class is sound, but its implementation needs refinement to bridge the gap between your custom filter logic and the actual Eloquent Query Builder.
In a well-structured application, the builder object represents the active database query you are building (e.g., `Thread::query()`). Your goal should be to modify this builder *before* execution.
**What `$builder` is doing:**
The `$builder` property is intended to hold a reference to the underlying Eloquent Query Builder instance obtained from the model (e.g., `Thread::query()` or `Thread::where(...)`). By passing `$this->builder` into your filter methods, you are correctly ensuring that any action taken within `by($username)` modifies the *same* query object being constructed by the controller.
However, as seen in your implementation, the logic inside `apply()` is overly complex and relies on non-existent helper functions (`method_exist`). A cleaner approach is to let the specific filter classes directly interact with the builder instance they receive.
## The Idiomatic Laravel Solution: Applying Filters Directly
Instead of relying on a generic `filter()` method, we should leverage the power of Eloquent's chainable methods (`where`, `orderBy`) within our custom filter mechanism. This ensures that the resulting query is valid and optimized by Laravel.
Here is how you can refactor your approach to correctly implement dynamic filtering:
### Refactored Approach Example
We will discard the abstract `filter()` method in favor of having the filter classes directly modify the builder object passed to them.
**1. Update the Controller:** The controller should be responsible for initiating the query and then passing it to the filters.
```php
// threadscontroller.php (Refactored)
public function index(Channel $channel, ThreadFilters $filters)
{
// Start with the base query builder
$query = Thread::latest();
// Apply filters by chaining methods directly
$query = $filters->apply($query); // Pass the builder to the filter class
if ($channel->exists) {
$query->where('channel_id', $channel->id);
}
$threads = $query->get();
return view('threads.index', compact('threads'));
}
```
**2. Refactor the Filter Class:** The filter class now focuses solely on adding constraints, making it clearer and more maintainable.
```php
// threadfilters.php (Refactored)
namespace App\Filters;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Builder; // Import the Builder type
class ThreadFilters extends Filters
{
protected $filters = ['by'];
protected function by($username, Builder $builder) // Accept the builder instance
{
// Use the passed builder to apply constraints
$user = User::where('name', $username)->firstOrFail();
// Apply the constraint directly using the provided builder object
$builder->where('user_id', $user->id);
}
}
```
## Conclusion
The error you faced was a classic case of trying to force custom application logic into an existing method signature. In Laravel, the best practice is to keep your filtering logic aligned with Eloquent's fluent interface. By ensuring your filter classes interact directly with the Query Builder instance (`$builder`)—rather than attempting to call non-existent methods on it—you create code that is not only functional but also highly readable and adheres to Laravel's architectural principles.
For deeper insights into building scalable data layers in Laravel, always refer to the official documentation and explore advanced Eloquent features found on [laravelcompany.com](https://laravelcompany.com).