Originally published on laravel.learnio.dev — part of the Laravel best practices course. View this lesson on the original site.
Control exactly how route values resolve
Implicit binding covers common cases. Custom binding is for cases where lookup rules are domain-specific.
Explicit model binding in a service provider
In AppServiceProvider::boot:
use App\Models\User;
use Illuminate\Support\Facades\Route;
public function boot(): void
{
Route::model('user', User::class);
}
This explicitly maps {user} parameters to User resolution.
Custom resolver with Route::bind
Use this when lookups need custom rules:
Route::bind('user', function (string $value) {
return User::where('username', $value)->firstOrFail();
});
Now /users/stuart can resolve by username instead of numeric id.
Model-level binding logic
If a model always needs special lookup logic, keep it on the model:
public function resolveRouteBinding($value, $field = null)
{
return $this->query()
->where($field ?? $this->getRouteKeyName(), $value)
->where('is_active', true)
->firstOrFail();
}
This keeps controller actions thin while enforcing consistent lookup rules.
Bad way vs better way
Bad way (repeat custom query in many actions):
public function show(string $username)
{
$user = User::where('username', $username)->firstOrFail();
// ...
}
Better way (central custom binding once):
Route::bind('user', function (string $value) {
return User::where('username', $value)->firstOrFail();
});
public function show(User $user)
{
// ...
}
Why better:
- custom lookup rule is defined once,
- controllers stop duplicating query logic,
- route behaviour stays consistent across the app.
When to choose each approach
- use implicit binding for standard id or slug lookups,
- use
Route::bindfor parameter-specific custom rules, - use
resolveRouteBindingwhen model-wide behaviour should apply everywhere.
Custom binding flow
flowchart LR
A[Route parameter value] --> B[Custom binding logic]
B --> C[Resolved model]
C --> D[Controller action]
B --> E[404 if unresolved]
Next, you will combine route binding with middleware boundaries to enforce access rules before controller logic runs.
From Laravel best practices — Routing, Controllers & Request Flow