Generating your first slug | laravel-sluggable | Spatie

 SPATIE

  Laravel Sluggable
====================

spatie.be/open-source

  [Docs](https://spatie.be/docs)  [Laravel-sluggable](https://spatie.be/docs/laravel-sluggable/v4)  Basic-usage  Generating your first slug

 Version   v4

 Other versions for crawler [v4](https://spatie.be/docs/laravel-sluggable/v4)

  Generating your first slug
- [ Introduction ](https://spatie.be/docs/laravel-sluggable/v4/introduction)
- [ Requirements ](https://spatie.be/docs/laravel-sluggable/v4/requirements)
- [ Installation &amp; setup ](https://spatie.be/docs/laravel-sluggable/v4/installation-setup)
- [ Translatable slugs ](https://spatie.be/docs/laravel-sluggable/v4/translatable-slugs)
- [ Laravel Boost skill ](https://spatie.be/docs/laravel-sluggable/v4/laravel-boost-skill)
- [ Changelog ](https://spatie.be/docs/laravel-sluggable/v4/changelog)
- [ Questions and issues ](https://spatie.be/docs/laravel-sluggable/v4/questions-issues)
- [ Support us ](https://spatie.be/docs/laravel-sluggable/v4/support-us)
- [ Upgrading ](https://spatie.be/docs/laravel-sluggable/v4/upgrading)

Basic usage
-----------

- [ Generating your first slug ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/getting-started)
- [ Using the Sluggable attribute ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/using-the-attribute)
- [ Using the HasSlug trait ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/using-the-has-slug-trait)
- [ Finding models by slug ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/finding-models-by-slug)
- [ Self-healing URLs ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/self-healing-urls)

Advanced usage
--------------

- [ Combining multiple source columns ](https://spatie.be/docs/laravel-sluggable/v4/advanced-usage/source-fields)
- [ Tuning the uniqueness suffix ](https://spatie.be/docs/laravel-sluggable/v4/advanced-usage/uniqueness)
- [ Overriding the underlying actions ](https://spatie.be/docs/laravel-sluggable/v4/advanced-usage/overriding-actions)

 Generating your first slug
==========================

###  On this page

1. [ 1. Add the attribute to the model ](#content-1-add-the-attribute-to-the-model)
2. [ 2. Add a slug column to the migration ](#content-2-add-a-slug-column-to-the-migration)
3. [ 3. Use it ](#content-3-use-it)
4. [ 4. Wire it into a route with self-healing (optional) ](#content-4-wire-it-into-a-route-with-self-healing-optional)
5. [ What's next ](#content-whats-next)
6. [ Let the Laravel Boost skill set things up for you ](#content-let-the-laravel-boost-skill-set-things-up-for-you)

This walkthrough takes a `Post` model from nothing to a working slug in three steps: the `#[Sluggable]` attribute, a migration, and a query. An optional fourth step wires the slug into a route with self-healing URLs so renaming a model never breaks an old link.

1. Add the attribute to the model
---------------------------------------------------------------------------------------------------------------------------------------------------------

Place `#[Sluggable]` on the model class and tell it which column to read from and which to write to.

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Sluggable\Attributes\Sluggable;

#[Sluggable(from: 'title', to: 'slug')]
class Post extends Model
{
}
```

That's all the configuration the package needs. Its service provider listens for Eloquent's `creating` and `updating` events and reads the attribute at runtime, so no trait or extra registration is required.

2. Add a slug column to the migration
---------------------------------------------------------------------------------------------------------------------------------------------------------------------

The package writes the slug into the column you named in the attribute, so the table needs a matching string column.

```
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('slug')->unique();
            $table->timestamps();
        });
    }
};
```

The `unique()` constraint is optional. The package appends `-1`, `-2`, etc. on collisions whether or not the database enforces uniqueness, but the constraint is a useful safety net for code paths that bypass Eloquent.

3. Use it
---------------------------------------------------------------------------------

Slugs are written when the model is created and regenerated whenever the source field changes.

```
$post = Post::create(['title' => 'Hello World']);
$post->slug; // "hello-world"

$post->update(['title' => 'Hello Universe']);
$post->slug; // "hello-universe"
```

4. Wire it into a route with self-healing (optional)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

This step is optional. Skip it if you don't need slugs in your URLs, or if your slugs genuinely never change after creation (see [Self-healing URLs](/docs/laravel-sluggable/v4/basic-usage/self-healing-urls#when-you-dont-need-self-healing) for plain `{post:slug}` binding).

Most user-editable content (blog posts, articles, products, documentation pages, events) eventually gets renamed, and a renamed slug breaks every existing link unless you opt into self-healing URLs. With self-healing enabled the route key becomes `-{id}`, the primary key drives the lookup, and a stale slug `308`-redirects to the canonical URL instead of returning a `404`.

To follow along, add `selfHealing: true` to the attribute on the `Post` model from step 1 and add `use HasSlug;` to the class. The trait is required because self-healing has to override `getRouteKey()` and `resolveRouteBinding()`.

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Sluggable\Attributes\Sluggable;
use Spatie\Sluggable\HasSlug;

#[Sluggable(
    from: 'title',
    to: 'slug',
    selfHealing: true,
)]
class Post extends Model
{
    use HasSlug;
}
```

Bind the model to a route the usual way; the package handles the route key for you.

```
// routes/web.php
Route::get('/posts/{post}', fn (Post $post) => view('posts.show', compact('post')));
```

A request to `/posts/hello-world-5` resolves the post; renaming it later doesn't break the link. See [Self-healing URLs](/docs/laravel-sluggable/v4/basic-usage/self-healing-urls) for the full mechanics, including the plain `{post:slug}` alternative for slugs that genuinely never change.

What's next
---------------------------------------------------------------------------------------

That is enough for most projects. From here you can:

- Tune the generation rules (separator, language, uniqueness, when to skip): see the rest of this section.
- Look slugs up directly with [`findBySlug()`](/docs/laravel-sluggable/v4/basic-usage/finding-models-by-slug).
- Translate slugs per locale with [`HasTranslatableSlug`](/docs/laravel-sluggable/v4/translatable-slugs).

Let the Laravel Boost skill set things up for you
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

If your project uses [Laravel Boost](https://github.com/laravel/boost), this package ships a [Boost skill](/docs/laravel-sluggable/v4/laravel-boost-skill) that teaches Boost-aware AI assistants (Claude Code, Cursor, Copilot CLI, Gemini CLI, and others) how to scaffold every step above, including the optional self-healing route binding. Ask your assistant something like "set up sluggable on the Post model" and it will write the migration, add the attribute, and wire up the route.

 A good
match?
-------------

### What we do best

- All things Laravel
- Custom frontend components
- Building APIs
- AI-powered features
- Simplifying things
- Clean solutions
- Integrating services

### Not our cup of tea

- WordPress themes
- Cutting corners
- Free mockups to win a job
- "Just execute the briefing"

 In short: we'd like to be a **substantial part** of your project.

 [ Get in touch via email ](mailto:info@spatie.be?subject=A%20good%20match%21&body=Tell%20us%20as%20much%20as%20you%20can%20about%0A-%20your%20online%20project%0A-%20your%20planning%0A-%20your%20budget%0A-%20%E2%80%A6%0A%0AAnything%20that%20helps%20us%20to%20start%20straightforward%21)
