Installation:
composer require bpocallaghan/sluggable
No additional configuration is required beyond adding the trait to your model.
Basic Usage:
Add the HasSlug trait to any Eloquent model:
use Bpocallaghan\Sluggable\HasSlug;
class Post extends Model
{
use HasSlug;
}
The slug will auto-generate from the name attribute (default) and save to a slug column (default).
First Use Case:
$post = new Post(['name' => 'My Awesome Post']);
$post->save();
// $post->slug now contains "my-awesome-post"
HasSlug and SlugOptions in the README.str_slug() (Laravel’s built-in helper) for generation and defaults to name → slug.slug column (e.g., string or text).Default Slug Generation:
// Auto-generates slug from 'title' and saves to 'slug_field'
class Article extends Model
{
use HasSlug;
protected function getSlugOptions()
{
return SlugOptions::create()
->generateSlugFrom('title')
->saveSlugTo('slug_field');
}
}
Dynamic Slug Sources: Use computed attributes or relationships for slugs:
class Product extends Model
{
use HasSlug;
public function getSlugSourceAttribute()
{
return "{$this->name} ({$this->category->name})";
}
protected function getSlugOptions()
{
return SlugOptions::create()
->generateSlugFrom('slug_source');
}
}
Custom Separators/Transformations:
Override str_slug behavior via SlugOptions:
protected function getSlugOptions()
{
return SlugOptions::create()
->slugSeparator('_')
->generateSlugFrom('name')
->lowercaseSlug(true); // Optional: Force lowercase
}
Manual Slug Updates: Trigger slug regeneration without saving:
$post->updateSlug(); // Manually regenerate slug
Form Handling:
Use updateSlug() in form submissions to refresh slugs if the source field changes:
public function update(StorePostRequest $request)
{
$post->fill($request->validated());
$post->updateSlug(); // Ensure slug is updated
$post->save();
}
API Responses: Include slugs in API resources:
public function toArray($request)
{
return [
'id' => $this->id,
'slug' => $this->slug, // Auto-included
'title' => $this->title,
];
}
Scopes/Queries: Use slugs for SEO-friendly routes or scopes:
public function scopeActive($query)
{
return $query->where('is_active', true)
->where('slug', 'like', '%active%');
}
Testing: Mock slug generation in tests:
$post = new Post(['name' => 'Test Post']);
$post->shouldReceive('generateSlug')->andReturn('test-post');
$post->save();
Column Mismatch:
Column 'slug' does not exist.slug column (or override saveSlugTo()).Schema::string('slug') or Schema::text('slug') for the column.Duplicate Slugs:
my-post-2).SlugOptions::duplicateSlugSeparator() if needed:
->duplicateSlugSeparator('-v') // Generates "my-post-v2"
Case Sensitivity:
Str::slug() is case-insensitive by default, but some databases (e.g., MySQL with utf8mb4_bin) treat slugs as case-sensitive.->lowercaseSlug(true) in SlugOptions or enforce case in migrations:
$table->string('slug')->collation('utf8mb4_general_ci');
Relationship Delays:
with():
$post->load('category'); // Ensure relationship is loaded
$post->save();
Mass Assignment:
$fillable.slug from $fillable or use $guarded:
protected $fillable = ['name', 'content']; // Exclude 'slug'
Log Slug Generation:
Temporarily add a generatingSlug event listener to debug:
Sluggable::generatingSlug(function ($model, $slug) {
\Log::debug("Generating slug for {$model->class}: {$slug}");
});
Check for Overrides:
Ensure no other package or model method is interfering with save() or generateSlug().
Custom Slug Logic:
Override the generateSlug() method in your model:
protected function generateSlug($value)
{
return parent::generateSlug($value) . '-custom';
}
Event Hooks:
Listen for sluggable.slug.generated events:
Sluggable::slugGenerated(function ($model, $slug) {
// Post-process the slug (e.g., cache it)
});
Database-Level Uniqueness:
Add a unique index to the slug column in migrations:
$table->string('slug')->unique();
Localization:
Generate language-specific slugs by overriding getSlugOptions() dynamically:
protected function getSlugOptions()
{
return Sluggable::create()
->generateSlugFrom("title_{$this->locale}");
}
Avoid Regeneration: Skip slug generation if the source attribute hasn’t changed:
public function save(array $options = [])
{
if (!$this->isDirty('name')) {
$options['skipSlugGeneration'] = true;
}
return parent::save($options);
}
Batch Processing: For bulk operations, regenerate slugs in a queue job to avoid timeouts:
Post::chunk(100, function ($posts) {
foreach ($posts as $post) {
$post->updateSlug();
$post->save();
}
});
How can I help you explore Laravel packages today?