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

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.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Install the package**:
   ```bash
   composer require spatie/laravel-sluggable

No additional configuration is required—just publish the package if you need to customize behavior (e.g., php artisan vendor:publish --provider="Spatie\Sluggable\SluggableServiceProvider").

  1. Add the #[Sluggable] attribute to your Eloquent model:

    use Spatie\Sluggable\Attributes\Sluggable;
    
    #[Sluggable(from: 'title', to: 'slug')]
    class Post extends Model {}
    
    • from: The source attribute (e.g., title).
    • to: The column where the slug will be stored (e.g., slug).
  2. Add a slug column to your migration:

    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->string('slug')->unique(); // Optional but recommended
        $table->timestamps();
    });
    
  3. Use it:

    $post = Post::create(['title' => 'Hello World']);
    echo $post->slug; // "hello-world"
    

First Use Case: Basic Slug Generation

  • Slugs are automatically generated on create() and update() if the source field (title) changes.
  • No manual intervention needed—ideal for blog posts, articles, or products where titles are user-editable.

Implementation Patterns

1. Attribute vs. Trait

  • Use #[Sluggable] for simple cases (static from/to mappings).
  • Use HasSlug trait for advanced logic (closures, conditional generation, custom suffixes):
    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')
                ->skipGenerateWhen(fn () => $this->isDraft());
        }
    }
    

2. Self-Healing URLs

Enable for dynamic content (e.g., blog posts) to avoid broken links when titles change:

#[Sluggable(from: 'title', to: 'slug', selfHealing: true)]
class Post extends Model {
    use HasSlug; // Required for self-healing
}
  • Route binding: {post:slug-id} (e.g., /posts/hello-world-5).
  • Stale slugs: Automatically redirect to the canonical URL (e.g., /posts/old-slug-5/posts/new-slug-5).

3. Finding Models by Slug

Leverage the findBySlug() helper:

$post = Post::findBySlug('hello-world'); // Finds by slug
$post = Post::findBySlug('hello-world', ['id', 'title']); // Select specific columns
$post = Post::findBySlug('hello-world', ['*'], fn ($query) => $query->where('published', true));

4. Translatable Slugs

Combine with spatie/laravel-translatable:

use Spatie\Sluggable\HasTranslatableSlug;

class Post extends Model {
    use HasTranslatableSlug;

    public function getSlugOptions(): SlugOptions {
        return SlugOptions::create()
            ->generateSlugsFrom('title')
            ->saveSlugsTo('slug');
    }
}
  • Generates locale-specific slugs (e.g., es.slug, fr.slug).

5. Custom Suffixes for Collisions

Override default -1, -2 suffixes:

->usingSuffixGenerator(fn (string $slug, int $iteration) => bin2hex(random_bytes(4)))

6. Conditional Slug Generation

Skip slug updates for drafts or specific states:

->skipGenerateWhen(fn () => $this->state === 'draft')

7. Scoped Uniqueness

Ensure slugs are unique within a scope (e.g., tenant_id):

->extraScope(fn ($query) => $query->where('tenant_id', $this->tenant_id))

Gotchas and Tips

Pitfalls

  1. Missing HasSlug Trait for Self-Healing

    • Self-healing requires use HasSlug; to override getRouteKey() and resolveRouteBinding().
    • Fix: Add the trait if using selfHealing: true.
  2. Slug Column Not Unique

    • The package handles collisions with -1, -2, etc., but a UNIQUE constraint in the DB prevents duplicate slugs at the database level.
    • Tip: Add ->unique() to the slug column in migrations for safety.
  3. Case Sensitivity in Slugs

    • Slugs are lowercase by default. Use ->generateSlugsFrom(fn ($title) => strtoupper($title)) for uppercase slugs.
    • Gotcha: Database collations (e.g., utf8mb4_unicode_ci) may affect uniqueness checks.
  4. Translatable Slugs Without Translatable Package

    • HasTranslatableSlug requires spatie/laravel-translatable. Install it first:
      composer require spatie/laravel-translatable
      
  5. Route Binding Conflicts

    • If using selfHealing: false, ensure your route binding matches the slug column:
      Route::get('/posts/{post:slug}', [PostController::class, 'show']);
      
    • Tip: Use selfHealing: true for dynamic content to avoid broken links.
  6. Performance with Large Datasets

    • Uniqueness checks run on every save. For high-traffic apps, consider:
      • Adding a UNIQUE index on the slug column.
      • Using extraScope() to limit the uniqueness check to a subset of records.

Debugging Tips

  1. Check Slug Generation

    • Override generateSlug() to log the generated slug:
      public function generateSlug(): void {
          $slug = $this->getSlugOptions()->generateSlug($this);
          logger()->debug("Generated slug: {$slug}");
          $this->forceFill(['slug' => $slug]);
      }
      
  2. Verify Route Binding

    • Test self-healing with:
      Route::get('/posts/{post:slug-id}', [PostController::class, 'show']);
      
    • Use php artisan route:list to confirm the binding works.
  3. Handle Collisions Manually

    • If the default suffix logic isn’t sufficient, implement a custom suffix generator:
      ->usingSuffixGenerator(fn ($slug, $iteration) => "-{$iteration}-custom")
      

Extension Points

  1. Custom Slug Generator

    • Replace the default generator via config (config/sluggable.php):
      'generator' => \App\Services\CustomSlugGenerator::class,
      
    • Implement Spatie\Sluggable\SlugGenerator interface.
  2. Override Self-Healing Logic

    • Customize redirects or route keys by extending Spatie\Sluggable\SelfHealingUrlGenerator.
  3. Add Slug to API Responses

    • Use Laravel’s resource classes or API responses to include slugs:
      public function toArray() {
          return [
              'id' => $this->id,
              'slug' => $this->slug,
              'title' => $this->title,
          ];
      }
      
  4. Bulk Slug Regeneration

    • Regenerate slugs for all records in a table:
      Post::all()->each(fn ($post) => $post->generateSlug()->save());
      

Pro Tips

  • Use Laravel Boost: If using Laravel Boost, ask your AI assistant to "set up sluggable on the Post model" to auto-generate the migration, attribute, and route binding.
  • Combine with spatie/laravel-activitylog: Track slug changes for auditing:
    use Spatie\Activitylog\LogOptions;
    
    protected static $logAttributes = ['slug'];
    
  • Test Slug Generation: Add tests for edge cases (e.g., special characters, empty titles):
    public function test_slug_generation_with_special_chars() {
        $post = Post::create(['title' => 'Hello @World!']);
        $this->assertEquals('hello-world', $post->slug);
    }
    
  • Cache Slugs: For read-heavy apps, cache slugs in Redis or the file system to avoid DB queries:
    $slug = cache()->remember("slug-{$post->id}", now()->addHours(1
    
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.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai