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::bind for parameter-specific custom rules,
  • use resolveRouteBinding when 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