# Laravel Sluggable Laravel Sluggable is a PHP package by Spatie that provides automatic URL-friendly slug generation for Laravel Eloquent models. When you save a model, the package automatically generates a unique slug from specified source fields using Laravel's `Str::slug` method, converting spaces to hyphens and handling special characters appropriately. The package offers extensive configuration options including multiple source fields, custom separators, maximum length limits, language-specific slug generation, and scoped uniqueness. It also integrates seamlessly with Spatie's laravel-translatable package to generate localized slugs for multilingual applications. The fluent API makes it easy to customize slug behavior for different use cases while maintaining clean, readable code. ## Installation Install the package via Composer. ```bash composer require spatie/laravel-sluggable ``` ## Basic Model Setup with HasSlug Trait The `HasSlug` trait provides automatic slug generation when creating or updating Eloquent models. You must implement the `getSlugOptions()` method to configure the slug source and destination fields. ```php generateSlugsFrom('name') ->saveSlugsTo('slug'); } } // Usage $article = new Article(); $article->name = 'My First Blog Post'; $article->save(); echo $article->slug; // Output: "my-first-blog-post" ``` ## Migration Setup Create a migration with a slug field to store the generated slugs. ```php id(); $table->string('name'); $table->string('slug'); $table->timestamps(); $table->unique('slug'); }); } public function down() { Schema::dropIfExists('articles'); } } ``` ## SlugOptions::create() Static factory method to create a new SlugOptions configuration instance with default settings. ```php generateSlugsFrom('title') ->saveSlugsTo('slug'); // Default options: // - generateUniqueSlugs: true // - maximumLength: 250 // - slugSeparator: '-' // - slugLanguage: 'en' // - generateSlugsOnCreate: true // - generateSlugsOnUpdate: true ``` ## generateSlugsFrom() Specifies which model field(s) or callable should be used to generate the slug. Accepts a string, array of strings, or a callable function. ```php generateSlugsFrom('name') ->saveSlugsTo('slug'); } // Multiple fields public function getSlugOptions(): SlugOptions { return SlugOptions::create() ->generateSlugsFrom(['first_name', 'last_name']) ->saveSlugsTo('slug'); } // "John Doe" -> "john-doe" // Using a callable for custom logic public function getSlugOptions(): SlugOptions { return SlugOptions::create() ->generateSlugsFrom(function ($model) { return "{$model->category->name} {$model->title}"; }) ->saveSlugsTo('slug'); } // Category: "Tech", Title: "New Features" -> "tech-new-features" ``` ## saveSlugsTo() Defines the database field where the generated slug will be stored. ```php generateSlugsFrom('name') ->saveSlugsTo('url_slug'); // Custom field name } // Model with 'url_slug' field $product = Product::create(['name' => 'Super Widget']); echo $product->url_slug; // Output: "super-widget" ``` ## allowDuplicateSlugs() Disables automatic unique slug generation. By default, the package appends a number suffix to ensure uniqueness. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->allowDuplicateSlugs(); } // Without allowDuplicateSlugs (default): // First: "hello-world" // Second: "hello-world-1" // Third: "hello-world-2" // With allowDuplicateSlugs: // First: "hello-world" // Second: "hello-world" // Third: "hello-world" ``` ## slugsShouldBeNoLongerThan() Sets the maximum length for the generated slug. The slug source string is truncated before slug generation. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->slugsShouldBeNoLongerThan(50); } // Long title gets truncated $article = Article::create([ 'name' => 'This Is An Extremely Long Article Title That Would Be Truncated' ]); echo $article->slug; // Output: "this-is-an-extremely-long-article-title-that-woul" // Note: Uniqueness suffix may make it slightly longer than 50 ``` ## usingSeparator() Changes the separator character used in slugs. Default is hyphen (-). ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->usingSeparator('_'); } $item = Item::create(['name' => 'Hello World']); echo $item->slug; // Output: "hello_world" // Duplicates with underscore separator $item2 = Item::create(['name' => 'Hello World']); echo $item2->slug; // Output: "hello_world_1" ``` ## usingLanguage() Sets the language used by Laravel's Str::slug method for language-specific character replacements. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->usingLanguage('de'); } // German umlauts are handled appropriately $article = Article::create(['name' => 'Größe über alles']); echo $article->slug; // Output: "groesse-ueber-alles" // Dutch language public function getSlugOptions(): SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->usingLanguage('nl'); } ``` ## doNotGenerateSlugsOnCreate() Prevents automatic slug generation when the model is first created. Useful when you want manual control over initial slugs. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->doNotGenerateSlugsOnCreate(); } $article = Article::create(['name' => 'My Article']); echo $article->slug; // Output: null (no slug generated) // Manually trigger slug generation later $article->generateSlug(); $article->save(); echo $article->slug; // Output: "my-article" ``` ## doNotGenerateSlugsOnUpdate() Prevents automatic slug regeneration when the model is updated. Creates permalinks that remain stable. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->doNotGenerateSlugsOnUpdate(); } $article = Article::create(['name' => 'Original Title']); echo $article->slug; // Output: "original-title" $article->name = 'Updated Title'; $article->save(); echo $article->slug; // Output: "original-title" (unchanged) ``` ## preventOverwrite() Prevents slug generation if the slug field already has a value. Useful for preserving manually set slugs. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->preventOverwrite(); } // Slug is generated when field is null $article = Article::create(['name' => 'My Article']); echo $article->slug; // Output: "my-article" // Manually set slug is preserved $article2 = Article::create([ 'name' => 'Another Article', 'slug' => 'custom-slug' ]); echo $article2->slug; // Output: "custom-slug" (not overwritten) ``` ## skipGenerateWhen() Conditionally skips slug generation based on a callable that returns true/false. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->skipGenerateWhen(fn () => $this->state === 'draft'); } // Slug not generated for drafts $draft = Article::create(['name' => 'Draft Post', 'state' => 'draft']); echo $draft->slug; // Output: null // Slug generated for published articles $published = Article::create(['name' => 'Published Post', 'state' => 'published']); echo $published->slug; // Output: "published-post" ``` ## extraScope() Adds additional query constraints when checking for slug uniqueness. Useful for multi-tenant applications or scoped uniqueness. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->extraScope(fn ($builder) => $builder->where('website_id', $this->website_id)); } } // Slugs are unique within each website $page1 = Page::create(['name' => 'About', 'website_id' => 1]); echo $page1->slug; // Output: "about" $page2 = Page::create(['name' => 'About', 'website_id' => 2]); echo $page2->slug; // Output: "about" (same slug, different website) $page3 = Page::create(['name' => 'About', 'website_id' => 1]); echo $page3->slug; // Output: "about-1" (unique within website 1) ``` ## startSlugSuffixFrom() Sets the starting number for slug uniqueness suffixes. Default is 1. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->startSlugSuffixFrom(2); } Article::create(['name' => 'Hello World']); // slug: "hello-world" Article::create(['name' => 'Hello World']); // slug: "hello-world-2" Article::create(['name' => 'Hello World']); // slug: "hello-world-3" ``` ## useSuffixOnFirstOccurrence() Forces the first slug to also have a suffix, ensuring consistent naming patterns. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->useSuffixOnFirstOccurrence(); } // All slugs get suffixes, including the first Article::create(['name' => 'Example']); // slug: "example-1" Article::create(['name' => 'Example']); // slug: "example-2" Article::create(['name' => 'Example']); // slug: "example-3" ``` ## usingSuffixGenerator() Provides a custom callable for generating uniqueness suffixes instead of incrementing numbers. ```php generateSlugsFrom('name') ->saveSlugsTo('slug') ->usingSuffixGenerator( fn (string $slug, int $iteration) => bin2hex(random_bytes(4)) ); } // Random hex suffixes instead of numbers Article::create(['name' => 'Post']); // slug: "post" Article::create(['name' => 'Post']); // slug: "post-a1b2c3d4" Article::create(['name' => 'Post']); // slug: "post-e5f6g7h8" // UUID-based suffix public function getSlugOptions(): SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->usingSuffixGenerator( fn (string $slug, int $iteration) => substr(md5($slug . $iteration . time()), 0, 8) ); } ``` ## generateSlug() Manually triggers slug regeneration on the model. Call save() afterward to persist. ```php slug; // Output: "old-title" $article->name = 'New Title'; $article->generateSlug(); $article->save(); echo $article->slug; // Output: "new-title" // Useful with doNotGenerateSlugsOnUpdate() $article = Article::create(['name' => 'Original']); // Later, explicitly regenerate $article->name = 'Changed'; $article->generateSlug(); // Manually trigger $article->save(); ``` ## findBySlug() Static method to retrieve a model by its slug value. Supports translatable slugs with fallback locale. ```php where('published', true); }); // Returns null if not found $article = Article::findBySlug('non-existent'); // $article === null // For translatable models, searches current and fallback locale $article = TranslatableArticle::findBySlug('mon-article'); // Searches 'fr' and 'en' ``` ## Custom Slugs Override the automatically generated slug by setting it manually before saving. ```php 'My Article']); echo $article->slug; // Output: "my-article" // Override with custom slug $article->slug = 'custom-url-path'; $article->save(); echo $article->slug; // Output: "custom-url-path" // Set custom slug on create $article = new Article(); $article->name = 'Article Title'; $article->slug = 'my-custom-slug'; $article->save(); echo $article->slug; // Output: "my-custom-slug" ``` ## Route Model Binding Configure the model to use slugs for Laravel's implicit route model binding. ```php generateSlugsFrom('name') ->saveSlugsTo('slug'); } public function getRouteKeyName(): string { return 'slug'; } } // In routes/web.php Route::get('/articles/{article}', function (Article $article) { return view('articles.show', compact('article')); }); // URL: /articles/my-first-article // Laravel automatically resolves the Article model by slug ``` ## HasTranslatableSlug Trait Integrates with spatie/laravel-translatable to generate unique slugs for each locale. ```php generateSlugsFrom('name') ->saveSlugsTo('slug'); } } // Create with translations $article = new Article(); $article->setTranslation('name', 'en', 'My Article'); $article->setTranslation('name', 'fr', 'Mon Article'); $article->save(); echo $article->getTranslation('slug', 'en'); // Output: "my-article" echo $article->getTranslation('slug', 'fr'); // Output: "mon-article" ``` ## SlugOptions::createWithLocales() Creates SlugOptions for translatable slugs with a callable generator that receives the locale. ```php generateSlugsFrom(function ($model, $locale) { return "{$locale}-{$model->getTranslation('name', $locale)}"; }) ->saveSlugsTo('slug'); } } $article = new Article(); $article->setTranslation('name', 'en', 'Hello'); $article->setTranslation('name', 'nl', 'Hallo'); $article->setTranslation('name', 'fr', 'Bonjour'); $article->save(); echo $article->getTranslation('slug', 'en'); // Output: "en-hello" echo $article->getTranslation('slug', 'nl'); // Output: "nl-hallo" echo $article->getTranslation('slug', 'fr'); // Output: "fr-bonjour" ``` ## Translatable Route Model Binding HasTranslatableSlug provides automatic route binding resolution for the current locale. ```php generateSlugsFrom('name') ->saveSlugsTo('slug'); } public function getRouteKeyName(): string { return 'slug'; } } // In routes/web.php Route::get('/articles/{article}', function (Article $article) { return view('articles.show', compact('article')); }); // With app locale set to 'fr': // URL: /articles/mon-article // Resolves article by French slug automatically ``` ## Summary Laravel Sluggable is designed for any Laravel application requiring SEO-friendly URLs, content management systems, blogs, e-commerce platforms, or any system with human-readable identifiers. Common use cases include generating article URLs from titles, creating product slugs from names, building category hierarchies with unique paths, and implementing multilingual content with locale-specific slugs. The package handles edge cases like duplicate names, special characters, and multi-byte strings automatically. Integration follows standard Laravel patterns: install via Composer, add the trait to your models, implement the configuration method, and ensure your database has a slug column. The fluent configuration API allows combining multiple options like maximum length, custom separators, and scope constraints. For existing projects, you can disable automatic generation and trigger it manually, making migration straightforward. The package works seamlessly with Laravel's route model binding for clean controller code and integrates with spatie/laravel-translatable for multilingual applications.