I want URLs like /posts/my-first-post instead of /posts/12, and for nested routes /users/{user}/posts/{post} the post must belong to that user. How do I do it with route model binding?
DEV3 Developer Forum
How do I bind routes by slug instead of id in Laravel?
Every post is reviewed first. Usually reviewed within 24 hours.
1 reply
Route model binding can use any column, and scoped bindings protect nested routes.
Per-route custom key
Route::get('/posts/{post:slug}', [PostController::class, 'show']);
Model-wide default
class Post extends Model
{
public function getRouteKeyName(): string
{
return 'slug';
}
}
With this, route('posts.show', $post) also generates the slug.
Scoped bindings for nested resources
Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
return $post;
})->scopeBindings();
Laravel will query $user->posts() for the second parameter, so a post of another user returns 404. When you specify a custom key on a child ({post:slug}) scoping is applied automatically; scopeBindings() makes it explicit for id keys, and Route::resource(...)->scoped() does it for resource routes. The relation name is guessed from the plural camel-case parameter name (posts), so the parent model needs that relationship. Use ->withoutScopedBindings() on the route if you do not want the child scoped to the parent.
Custom lookup logic
public function resolveRouteBinding($value, $field = null)
{
return $this->where($field ?? 'slug', $value)->where('published', true)->firstOrFail();
}
Soft deleted models are 404 by default; opt in with ->withTrashed() on the route.
Pitfalls
- Make sure the slug column has a unique index.
- Binding uses the type-hint name matching the route parameter name.
{post}needs a$postargument. - Missing models throw
ModelNotFoundException, rendered as 404. Customize inbootstrap/app.phpwithwithExceptions.