Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Sluggable Laravel Package

spatie/laravel-sluggable

Automatically generate unique, URL-friendly slugs for Laravel Eloquent models on save. Configure slug sources and target fields via a simple HasSlug trait and SlugOptions, with built-in uniqueness handling using Laravel’s Str::slug.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Installation: Add the package via Composer:

    composer require spatie/laravel-sluggable
    

    Publish the config (if needed) with:

    php artisan vendor:publish --provider="Spatie\Sluggable\SluggableServiceProvider"
    
  2. Basic Setup: Add the HasSlug trait to your Eloquent model and implement getSlugOptions():

    use Spatie\Sluggable\HasSlug;
    use Spatie\Sluggable\SlugOptions;
    
    class Article extends Model
    {
        use HasSlug;
    
        public function getSlugOptions(): SlugOptions
        {
            return SlugOptions::create()
                ->generateSlugsFrom('title')
                ->saveSlugsTo('slug');
        }
    }
    
  3. Migration: Ensure your migration includes a slug column (e.g., string('slug')).

    Schema::create('articles', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->string('slug'); // <-- Add this
        $table->timestamps();
    });
    
  4. First Use Case: Save a model to auto-generate a slug:

    $article = new Article(['title' => 'How to Use Slugs']);
    $article->save();
    // $article->slug now contains "how-to-use-slugs"
    

Implementation Patterns

1. Route Integration

Use getRouteKeyName() for implicit route binding:

public function getRouteKeyName()
{
    return 'slug';
}

Now access models via /articles/how-to-use-slugs instead of /articles/1.


2. Multi-Field Slugs

Combine fields (e.g., first_name + last_name) for unique slugs:

public function getSlugOptions(): SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom(['first_name', 'last_name'])
        ->saveSlugsTo('slug');
}

3. Conditional Slug Generation

Skip slug generation for drafts or specific states:

public function getSlugOptions(): SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('title')
        ->saveSlugsTo('slug')
        ->skipGenerateWhen(fn () => $this->is_draft);
}

4. Custom Suffixes

Replace numeric suffixes with custom logic (e.g., UUIDs):

public function getSlugOptions(): SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('title')
        ->saveSlugsTo('slug')
        ->usingSuffixGenerator(fn ($slug, $iteration) => Str::uuid());
}

5. Translatable Slugs

For multilingual apps, use HasTranslatableSlug with laravel-translatable:

use Spatie\Sluggable\HasTranslatableSlug;

class Article extends Model
{
    use HasTranslations, HasTranslatableSlug;

    public $translatable = ['title', 'slug'];

    public function getSlugOptions(): SlugOptions
    {
        return SlugOptions::createWithLocales(['en', 'es'])
            ->generateSlugsFrom('title')
            ->saveSlugsTo('slug');
    }
}

6. Scoped Slugs

Add a scope (e.g., tenant_id) to avoid conflicts:

public function getSlugOptions(): SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->extraScope(fn ($query) => $query->where('tenant_id', $this->tenant_id));
}

7. Manual Regeneration

Force-update a slug without changing source fields:

$article->generateSlug(); // Recalculates slug
$article->save();

8. Querying by Slug

Use the findBySlug helper:

$article = Article::findBySlug('how-to-use-slugs');

Gotchas and Tips

Pitfalls

  1. Duplicate Slugs:

    • If allowDuplicateSlugs() is enabled, collisions may occur. Test edge cases (e.g., identical titles).
    • Fix: Use preventOverwrite() or custom suffixes.
  2. Case Sensitivity:

    • Slugs are case-insensitive by default (e.g., My-Slug = my-slug). Use Str::lower() in generateSlugsFrom if needed.
  3. Route Binding Conflicts:

    • Ensure getRouteKeyName() matches the saveSlugsTo column. Mismatches cause ModelNotFoundException.
  4. Translatable Pitfalls:

    • Forgetting to include the slug field in $translatable array breaks generation.
    • Fix: Verify public $translatable = ['slug', ...];.
  5. Performance:

    • Scoped queries (extraScope) add overhead. Avoid complex scopes in high-traffic routes.

Debugging Tips

  1. Log Slug Generation: Add a temporary dd() in HasSlug to inspect intermediate values:

    // In Spatie\Sluggable\HasSlug trait (override in a local copy if needed)
    protected function generateSlug()
    {
        $slug = Str::slug($this->getSlugSource());
        logger()->debug("Generated slug: {$slug} from source: {$this->getSlugSource()}");
        // ...
    }
    
  2. Check for Silent Failures:

    • If slugs aren’t updating, verify:
      • The model isn’t in a skipGenerateWhen state.
      • The saveSlugsTo column exists in the migration.
      • No preventOverwrite() is blocking updates.
  3. Test Edge Cases:

    • Empty strings: generateSlugsFrom('title')->skipGenerateWhen(fn () => empty($this->title)).
    • Special characters: Use usingLanguage() (e.g., 'nl' for Dutch umlauts).

Extension Points

  1. Custom Slug Logic: Override generateSlug() in your model:

    public function generateSlug()
    {
        $this->slug = Str::slug($this->title, '_') . '-' . $this->category_id;
    }
    
  2. Observer Integration: Trigger slug regeneration via an observer:

    class ArticleObserver
    {
        public function saved(Article $article)
        {
            if ($article->wasChanged('title')) {
                $article->generateSlug()->save();
            }
        }
    }
    
  3. API Responses: Use App\Services\SlugService to centralize slug logic:

    class SlugService
    {
        public static function generateFromTitle(string $title): string
        {
            return Str::slug($title, '-', 'en');
        }
    }
    
  4. Seeding: Pre-generate slugs in seeders to avoid race conditions:

    $article = Article::create(['title' => 'Seeded Article']);
    $article->slug = SlugService::generateFromTitle($article->title);
    $article->save();
    

Config Quirks

  • Default Separator: Hardcoded to - in Str::slug(). Override via usingSeparator('_').
  • Language Fallback: If usingLanguage() fails, defaults to en. Handle gracefully:
    ->usingLanguage(app()->getLocale())
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport