Skip to main content

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.

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:

FeatureNeeds
Short links (/go/{token})a bound PolyslugUrlResolver
Sitemapsthe same one
laravel/head canonical + hreflang tagsthe 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.

Worth reading before you need 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.