Quick start
Mark a model as sluggable, and you are done — slugs generate on save:
use Illuminate\Database\Eloquent\Model;
use Polyslug\Attributes\Polyslug;
use Polyslug\Concerns\HasPolyslug;
use Polyslug\Contracts\Sluggable;
#[Polyslug(source: 'title')]
class Page extends Model implements Sluggable
{
use HasPolyslug;
}
Point a route at it and add the canonical-redirect middleware:
use Illuminate\Support\Facades\Route;
Route::get('/pages/{page}', [PageController::class, 'show'])
->middleware('polyslug.canonical')
->name('pages.show');
That is the whole setup. Now:
$page = Page::create(['title' => 'Laravel Routing Explained']);
route('pages.show', $page); // → /pages/laravel-routing-explained_aB3xK
$page->update(['title' => 'A Deep Dive Into Laravel Routing']);
route('pages.show', $page); // → /pages/a-deep-dive-into-laravel-routing_aB3xK
// The old URL still works — and 301-redirects to the new one:
// GET /pages/laravel-routing-explained_aB3xK → 301 → /pages/a-deep-dive-into-laravel-routing_aB3xK
Scaffold a model instead
php artisan make:polyslug Page
The generated model is pre-wired with the attribute, the trait, and the interface. See
make:polyslug.
One more class, if you want short links or a sitemap
Everything above works with what the package ships. Three features need one thing more, because they build URLs outward rather than resolving them inward — and only your application knows what a page's URL looks like:
| Feature | Needs |
|---|---|
Short links (/go/{token}) | a bound PolyslugUrlResolver |
| Sitemaps | the same one |
laravel/head canonical + hreflang tags | the same one |
It is one method — given this model and this locale, what is its URL? — and you bind it once for all three. The URL resolver walks through writing it.
Two of those three fail silently without the binding: short links return 404, and the
head tags are simply not written. php artisan polyslug:doctor reports it, but knowing the
step exists is cheaper than diagnosing it later.
Where to go next
- How it works — what the route key is made of, and why resolution is by id rather than by slug.
- Sluggable models — every
#[Polyslug]option and the methods the trait adds. - Self-healing routes — the
Route::polyslug()macro, the redirect rules, and which requests are left untouched. - Access control — how to make sure a slug only ever resolves to a row this visitor is allowed to see.
- The URL resolver — the one class you write yourself, and what each feature does without it.
- Recipes — twelve app shapes wired end to end.