Custom Eloquent Builders are one of the most underrated features in Laravel. They allow you to move complex query logic out of your models and controllers into dedicated builder classes, making your code cleaner, reusable, and easier to maintain.
If you've ever written the same where(), orderBy(), or filtering logic in multiple places, a custom builder is worth considering.
The Problem
Imagine you have a Post model. Throughout your application, you frequently need to fetch published posts.
Without a custom builder, you might write something like this repeatedly:
Post::query()
->where('status', 'published')
->where('published_at', '<=', now())
->latest()
->get();
Soon, this query appears in controllers, jobs, commands, and API endpoints. If the business rule changes, you'll have to update it everywhere.
The Better Approach
Create a dedicated builder.
<?php
namespace App\Models\Builders;
use Illuminate\Database\Eloquent\Builder;
class PostBuilder extends Builder
{
public function published(): static
{
return $this
->where('status', 'published')
->where('published_at', '<=', now());
}
public function featured(): static
{
return $this->where('is_featured', true);
}
}
Now tell your model to use it.
<?php
namespace App\Models;
use App\Models\Builders\PostBuilder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
public function newEloquentBuilder($query): Builder
{
return new PostBuilder($query);
}
}
Now your queries become much more expressive.
Post::query()
->published()
->featured()
->latest()
->get();
Why Use Custom Builders?
1. Reusable Query Logic
Instead of duplicating the same conditions across your application, define them once and reuse them everywhere.
2. Cleaner Models
Many developers add dozens of local scopes to a model.
scopePublished()
scopeFeatured()
scopePopular()
scopeArchived()
scopeTrending()
scopeVisible()
As your application grows, models become cluttered.
Moving query logic into a dedicated builder keeps the model focused on representing the entity rather than every possible query.
3. Better Readability
Compare these two queries.
Post::query()
->where('status', 'published')
->where('published_at', '<=', now())
->where('is_featured', true)
->latest();
vs
Post::query()
->published()
->featured()
->latest();
The second version reads almost like English.
4. Easier Maintenance
Suppose the definition of a published post changes.
Instead of updating dozens of queries, you only modify one method.
public function published(): static
{
return $this
->where('status', 'published')
->whereNotNull('published_at')
->where('published_at', '<=', now());
}
Every query using published() immediately benefits from the update.
5. Great for Large Applications
As your SaaS grows, you'll often have reusable filters like:
- Active users
- Paid subscriptions
- Verified customers
- Pending invoices
- Recent orders
- Completed payments
Custom builders keep these queries organized and easy to discover.
When Should You Use Them?
Custom builders are a great choice when:
- Multiple queries share the same filtering logic.
- Your model has too many local scopes.
- You want more expressive, chainable query methods.
- You're building medium-scale to large-scale Laravel applications or SaaS products.
For very small applications, local scopes are often sufficient. But as your codebase grows, custom builders provide a cleaner structure.
Final Thoughts
Custom Eloquent Builders are a simple yet powerful way to organize query logic in Laravel. They help reduce duplication, improve readability, and make your application easier to maintain as it grows.
If your models are filled with dozens of local scopes or you find yourself copying the same query conditions repeatedly, moving that logic into a custom builder can make a significant difference.
It's one of those Laravel features that many developers overlook, but once you start using it, you'll likely wonder how you managed without it.