TALL Tales

Using Slugs with Laravel Resource Routes

Laravel 11

Published 10 March 2025

When working with resource routes in Laravel, things are smooth as silk—until you want to use something other than the model’s ID in the URL. A common use case? Slugs.

In this post, I’ll walk you through how to use slugs with Laravel’s Route::resource() method, how to properly bind them, and how to keep your routes clean, readable, and working perfectly.


🔍 The Problem: You Want Slugs, Not IDs

Say you’ve got a Post model and your URLs look like this:

/posts/123

That’s fine for development, but not very user-friendly. You want this instead:

/posts/how-to-use-laravel-slugs

You could define a dedicated route:

Route::get('/posts/{post:slug}', [PostController::class, 'show']);

But what if you’re using a resource controller?

Route::resource('posts', PostController::class);

This generates routes like GET /posts/{post} (for the show method), but by default, {post} refers to the ID. So how do you make Laravel use the slug instead?


🧼 The Clean Solution: Use getRouteKeyName()

If you always want to look up posts by slug instead of ID, the cleanest way is to override getRouteKeyName() in your model:

// in app/Models/Post.php
public function getRouteKeyName(): string
{
    return 'slug';
}

This tells Laravel: “Whenever this model is route-bound, use the slug column instead of id.”

Now, when using:

Route::resource('posts', PostController::class);

…Laravel will automatically resolve routes like /posts/my-laravel-slug using the slug field.


🧠 Important: Controller Must Use Model Binding

To make this work, your controller method must use route model binding like this:

public function show(Post $post)
{
    return view('posts.show', compact('post'));
}

Do not manually fetch the model by ID or slug like this:

public function show(string $id) {
    $post = Post::findOrFail($id); // ← this bypasses the binding magic!
}

Let Laravel do the work.


🧩 What About ->parameters()?

You may have seen this:

Route::resource('posts', PostController::class)
    ->parameters(['posts' => 'post:slug']);

This is not necessary if you're using getRouteKeyName() in your model. In fact, ->parameters() is mainly used to rename route parameters (e.g. from {post} to {slug}), not to define how Laravel fetches the model.

So unless you want to rename the parameter for some reason (rare), you can skip this entirely.


⚠️ Gotcha: Passing Slugs to Routes

If you're linking to your resource route in Blade, make sure to pass the model, not the slug string:

✅ Correct:

<a href="{{ route('posts.show', $post) }}">
    {{ $post->title }}
</a>

❌ Incorrect:

<a href="{{ route('posts.show', $post->slug) }}"> <!-- Will not resolve correctly -->

Passing the model allows Laravel to call getRouteKeyName() and build the URL using the slug.


🗂️ Bonus Tip: Using Slugs with Single-Action Controllers

If you're using a single-action controller (like PostShow::class), you can still use slugs like this:

Route::get('/posts/{post:slug}', PostShow::class)->name('posts.show');

Just make sure you’re not also defining a Route::resource() for posts, or you’ll get conflicts.


✅ Summary

To use slugs in your resource routes:

  1. Add getRouteKeyName() to your model if you always want to use slugs.
  2. Use model binding in your controller (public function show(Post $post)).
  3. In Blade, pass the full model to route(), not just the slug.
  4. Avoid defining conflicting routes manually.
  5. Run php artisan route:list to double-check what’s being registered.

Comments aren't open just yet — but they’re coming soon.

Enjoyed this chapter? Explore more stories