Laravel Packages

Laravel Sluggable

Published
Laravel Sluggable image

Nuno Maduro released Laravel Sluggable, a package for automatic slug generation on Eloquent models. Rather than reaching for a trait or a base class, the package uses a single PHP attribute #[Sluggable], placed directly on the model.

Beyond the basics, it covers the edge cases that tend to surface in real apps: collision handling, scoped uniqueness (per-tenant, per-locale), multi-column sources, soft-deleted record collisions, and full Unicode and CJK transliteration.

Get Started

Install the package via Composer:

composer require nunomaduro/laravel-sluggable

Note: This package requires PHP 8.5+ and Laravel 13.5+

The quickest way to wire up a model is the included make:sluggable Artisan command:

php artisan make:sluggable Post

It reads your model's table schema, picks the most likely source column (title, name, headline, or subject), adds the #[Sluggable] attribute to the model class, and creates a migration in database/migrations:

use NunoMaduro\LaravelSluggable\Attributes\Sluggable;
 
#[Sluggable(from: 'title')]
class Post extends Model
{
}
Schema::table('posts', function (Blueprint $table) {
$table
->string('slug')
// ->nullable()
->unique()
->after('id');
});

Review the migration before running it. You may need to make the column nullable if the table already has rows before you run php artisan migrate.

After that, slug generation is automatic:

$post = Post::create(['title' => 'Laravel News Rocks']);
$post->slug; // "laravel-news-rocks"

Configuration

All options live on the attribute itself. The from and to parameters control which columns are read and written, and from accepts an array when you want to combine multiple columns:

#[Sluggable(from: ['first_name', 'last_name'], to: 'slug')]
class Author extends Model {}
 
$author = Author::create(['first_name' => 'John', 'last_name' => 'Tolkien']);
$author->slug; // "john-tolkien"

For multi-tenant apps or anything with per-scope uniqueness, pass a scope column (or an array of columns):

#[Sluggable(from: 'title', scope: 'team_id')]

Other notable options include maxLength to cap slug length, separator to swap out the dash, and onUpdating to regenerate the slug whenever the source column changes. By default, slugs are locked after creation — manually assigned slug values are always preserved regardless of this setting.

Error Handling

If a slug cannot be generated, the package throws a CouldNotGenerateSlugException. In HTTP contexts, it automatically renders as a 422 response with a validation-style error payload, so it drops into existing error handling without extra work. The error key defaults to the source column name but can be overridden via errorKey. Because it extends ValidationException, it won't show up in your application logs.

Unicode Support

The pipeline transliterates non-Latin scripts to readable Latin slugs and is domain-aware — values that look like hostnames or file paths keep their dots intact instead of being collapsed to dashes.

You can learn more about Laravel Sluggable on the projects GitHub repo.

Yannick Lyn Fatt photo

Staff Writer at Laravel News and Full stack web developer.

Sponsored

laravelcloud logo
Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Cloud

The latest

View all →
Pollora: Run WordPress Inside a Laravel Application image

Pollora: Run WordPress Inside a Laravel Application

Read article
Scheduled Session Garbage Collection in Laravel 13.39 image

Scheduled Session Garbage Collection in Laravel 13.39

Read article
Deferred Schema Loading in Filament v5.8 image

Deferred Schema Loading in Filament v5.8

Read article
Sloppy: Static Analysis for AI-Written PHP image

Sloppy: Static Analysis for AI-Written PHP

Read article
Mastering Laravel Testing: A Video Course on Laravel Tests image

Mastering Laravel Testing: A Video Course on Laravel Tests

Read article
NativePHP Mobile Security Advisories: Update to 4.6.1 image

NativePHP Mobile Security Advisories: Update to 4.6.1

Read article