spatie/laravel-sluggable
Generate unique slugs for Eloquent models on create/update. Supports collision suffixes, translatable slugs, and customizable slug options. Includes self-healing URLs that keep old links working via slug+ID route keys with 308 redirects to the canonical URL.
The HasSlug trait is the long form of the same configuration the #[Sluggable] attribute encodes. Add the trait, implement getSlugOptions(), and return a SlugOptions instance. With nothing else added the model behaves identically to one annotated #[Sluggable(from: 'title', to: 'slug')].
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
class Post extends Model
{
use HasSlug;
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('title')
->saveSlugsTo('slug');
}
}
Reach for the trait when you need any of the following. None can be expressed through static attribute arguments.
Compute the slug from arbitrary model state, including related models, by passing a closure to generateSlugsFrom().
return SlugOptions::create()
->generateSlugsFrom(fn (Post $post) => "{$post->author->name} {$post->title}")
->saveSlugsTo('slug');
skipGenerateWhen() accepts a closure that runs on every save. Returning true leaves the slug column untouched for that save.
return SlugOptions::create()
->generateSlugsFrom('title')
->saveSlugsTo('slug')
->skipGenerateWhen(fn () => $this->state === 'draft');
extraScope() narrows the uniqueness check, so two records can share a slug as long as they differ on the scope columns.
return SlugOptions::create()
->generateSlugsFrom('title')
->saveSlugsTo('slug')
->extraScope(fn ($query) => $query->where('tenant_id', $this->tenant_id));
Replace the default -1, -2, ... collision suffix with your own. The closure receives the base slug and the collision iteration.
return SlugOptions::create()
->generateSlugsFrom('title')
->saveSlugsTo('slug')
->usingSuffixGenerator(fn (string $slug, int $iteration) => bin2hex(random_bytes(4)));
The trait adds a static findBySlug() helper so you don't have to hand-write the where().
$post = Post::findBySlug('hello-world');
See Finding models by slug for the full signature.
The trait exposes a public generateSlug() method that forces regeneration outside the normal save lifecycle. Call save() afterwards to persist the new value.
$post->generateSlug();
$post->save();
HasTranslatableSlug (which uses HasSlug under the hood) generates one slug per locale. See Translatable slugs.
Self-healing requires the trait so it can override getRouteKey() and resolveRouteBinding(). The feature itself can be enabled through the attribute (selfHealing: true) or through the slug options (->selfHealing()). See Self-healing URLs.
How can I help you explore Laravel packages today?