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 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 and redirects.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Install the package**:
   ```bash
   composer require spatie/laravel-sluggable
  1. Publish the migration (if needed):

    php artisan vendor:publish --provider="Spatie\Sluggable\SluggableServiceProvider" --tag="migrations"
    

    Run the migration:

    php artisan migrate
    
  2. Annotate your model with the #[Sluggable] attribute:

    use Spatie\Sluggable\Attributes\Sluggable;
    
    #[Sluggable(from: 'title', to: 'slug')]
    class Post extends Model
    {
        // ...
    }
    
  3. First use case: Create a model with a title:

    $post = Post::create(['title' => 'My Awesome Post']);
    echo $post->slug; // Outputs: "my-awesome-post"
    

Key Files to Review

  • Model: Check the #[Sluggable] attribute configuration.
  • Migration: Ensure the slug column exists (default: string, nullable if onCreate: false).
  • Routes: Use Route::get('/posts/{post:slug}', ...) for slug-based routing.

Implementation Patterns

Common Workflows

  1. Basic Slug Generation

    #[Sluggable(from: 'name', to: 'slug')]
    class Product extends Model { ... }
    
    • Automatically generates slugs on create()/update() for the name field.
  2. Self-Healing URLs

    use Spatie\Sluggable\HasSlug;
    
    #[Sluggable(from: 'title', to: 'slug', selfHealing: true)]
    class Article extends Model
    {
        use HasSlug;
    }
    
    • Route keys become {slug}-{id} (e.g., /articles/hello-world-5).
    • Old URLs (e.g., /articles/old-slug-5) redirect to the new one (308 Permanent Redirect).
  3. Dynamic Slug Sources (Trait Version)

    use Spatie\Sluggable\HasSlug;
    use Spatie\Sluggable\SlugOptions;
    
    class User extends Model
    {
        use HasSlug;
    
        public function getSlugOptions(): SlugOptions
        {
            return SlugOptions::create()
                ->generateSlugsFrom(fn () => "{$this->first_name}-{$this->last_name}")
                ->saveSlugsTo('username');
        }
    }
    
  4. Scoped Uniqueness

    return SlugOptions::create()
        ->generateSlugsFrom('title')
        ->saveSlugsTo('slug')
        ->extraScope(fn ($query) => $query->where('category_id', $this->category_id));
    
    • Ensures slug uniqueness within a category.
  5. Conditional Slug Generation

    return SlugOptions::create()
        ->generateSlugsFrom('title')
        ->saveSlugsTo('slug')
        ->skipGenerateWhen(fn () => $this->is_draft);
    
    • Skips slug generation for draft models.
  6. Custom Suffixes

    return SlugOptions::create()
        ->generateSlugsFrom('title')
        ->saveSlugsTo('slug')
        ->usingSuffixGenerator(fn ($slug, $iteration) => "-v{$iteration}");
    
    • Generates suffixes like -v1, -v2 instead of -1, -2.
  7. Translatable Slugs

    use Spatie\Sluggable\HasTranslatableSlug;
    
    class Post extends Model
    {
        use HasTranslatableSlug;
    
        public function getSlugOptions(): SlugOptions
        {
            return SlugOptions::create()
                ->generateSlugsFrom('title')
                ->saveSlugsTo('slug');
        }
    }
    
    • Requires spatie/laravel-translatable. Stores one slug per locale (e.g., slug_en, slug_es).

Integration Tips

  1. Route Binding Use the findBySlug() helper for slug-based routes:

    Route::get('/posts/{post:slug}', [PostController::class, 'show']);
    
    public function show(Post $post) { ... } // Automatically resolves via slug.
    
  2. Manual Slug Regeneration Force-regenerate a slug (e.g., after a title edit):

    $post->generateSlug(); // Updates slug without saving.
    $post->save();        // Persists changes.
    
  3. Customizing Slug Generation Override the default Str::slug behavior via config:

    // config/sluggable.php
    'generator' => \App\Services\CustomSlugGenerator::class,
    
    class CustomSlugGenerator implements SlugGeneratorContract
    {
        public function generate(string $value, string $separator = '-', int $maxLength = 250): string
        {
            return strtolower(preg_replace('/[^a-z0-9]+/', $separator, $value));
        }
    }
    
  4. API Responses Include slugs in API responses for consistency:

    return PostResource::make($post)->additional(['slug' => $post->slug]);
    
  5. Testing Use refreshDatabase() and test slug generation:

    public function test_slug_generation()
    {
        $post = Post::factory()->create(['title' => 'Test Post']);
        $this->assertEquals('test-post', $post->slug);
    }
    

Gotchas and Tips

Pitfalls

  1. Missing HasSlug Trait for Self-Healing

    • Error: Class 'App\Models\Post' does not use 'HasSlug' trait but has selfHealing=true.
    • Fix: Add use Spatie\Sluggable\HasSlug; and include the trait in your model.
  2. Slug Collisions Without Uniqueness

    • Issue: If unique: false is set, duplicate slugs may break routes or queries.
    • Fix: Ensure unique: true (default) or handle duplicates manually.
  3. Case Sensitivity in Routes

    • Problem: /POSTS/HELLO-WORLD may not match hello-world slugs.
    • Fix: Use Laravel’s route model binding with lowercase slugs or add middleware to normalize case.
  4. Translatable Slugs Without Translatable Package

    • Error: Class 'App\Models\Post' uses 'HasTranslatableSlug' but lacks 'HasTranslations' trait.
    • Fix: Install spatie/laravel-translatable and add use Spatie\Translatable\HasTranslations.
  5. Performance with Large Datasets

    • Issue: findBySlug() may be slow if the slug column is not indexed.
    • Fix: Add an index to the slug column in your migration:
      $table->string('slug')->unique();
      $table->index('slug');
      
  6. Self-Healing URLs and Soft Deletes

    • Problem: Deleted models may still resolve via their ID in the route key.
    • Fix: Override resolveRouteBinding() to check for soft-deleted models:
      public function resolveRouteBinding($value, $field = null)
      {
          return $this->where('slug', $value)->whereNull('deleted_at')->firstOrFail();
      }
      

Debugging Tips

  1. Log Slug Generation Add a temporary observer to debug slugs:

    Post::observe(function ($post) {
        if ($post->wasRecentlyCreated || $post->wasChanged('title')) {
            logger()->debug('Slug generated:', ['slug' => $post->slug, 'title' => $post->title]);
        }
    });
    
  2. Check for Silent Failures

    • Ensure onCreate/onUpdate are true (default) if slugs aren’t updating.
    • Verify preventOverwrite: false (default) if slugs aren’t regenerating.
  3. Validate Slugs in Forms Add a rule to Laravel’s validation:

    use Illuminate\Validation\Rule;
    
    $request->validate([
        'title' => 'required|string',
        'slug'  => ['required', 'string', Rule::unique('posts')->ignore($post)],
    ]);
    
  4. Handle Edge Cases in Suffixes

    • If using custom suffixes, ensure they don’t conflict with existing slugs:
      ->usingSuffixGenerator(fn ($slug, $iteration) => "-{$iteration}")
      

Extension Points

  1. Custom Slug Generator Implement Spatie\Sluggable\SlugGeneratorContract for bespoke slug logic:
    class CustomSlugGenerator implements SlugGeneratorContract
    {
    
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.
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
ecotone/kafka
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata