laravel-slugify maintained by oliwol
🌀 Laravel Slugify
A tiny trait that gives your Eloquent models clean, automatic slugs — without setup, ceremony, or extra weight.
Attach it to a model, define the source attribute, and the trait quietly handles generation, updates and uniqueness.
🚀 Installation
Install the package via Composer:
composer require oliwol/laravel-slugify
⚡️ Quick Start
Using the PHP Attribute (recommended)
use Oliwol\Slugify\HasSlug;
use Oliwol\Slugify\Slugify;
#[Slugify(from: 'title', to: 'slug')]
class Post extends Model
{
use HasSlug;
}
Using method overrides
use Oliwol\Slugify\HasSlug;
class Post extends Model
{
use HasSlug;
public function getAttributeToCreateSlugFrom(): string
{
return 'title';
}
public function getRouteKeyName(): string
{
return 'slug';
}
}
Priority: Method overrides always take precedence over the
#[Slugify]attribute.
🛠️ Usage
Add the HasSlug trait to any Eloquent model where a slug should be automatically generated.
Configuration via #[Slugify] Attribute
The #[Slugify] attribute accepts two parameters:
from(required) — the attribute used to generate the slug (e.g.'name','title').to(optional) — the column to save the slug to. Falls back togetRouteKeyName()if omitted.
use Oliwol\Slugify\HasSlug;
use Oliwol\Slugify\Slugify;
// Full configuration via attribute
#[Slugify(from: 'name', to: 'slug')]
class Post extends Model
{
use HasSlug;
}
// Only 'from' — slug column is determined by getRouteKeyName()
#[Slugify(from: 'name')]
class Post extends Model
{
use HasSlug;
public function getRouteKeyName(): string
{
return 'slug';
}
}
Note: The
toparameter only controls where the slug is saved. For route model binding, you still need to overridegetRouteKeyName()separately on your model.
Configuration via methods
Alternatively, you can configure slug generation by overriding methods:
getAttributeToCreateSlugFrom()— the attribute used to generate the slug (e.g. name/title).getRouteKeyName()— the slug column for route model binding (e.g. slug).- Optionally
getAttributeToSaveSlugTo()— a different column to save the slug. - Optionally override
scopeSlugQuery()— scoping for uniqueness (e.g. per team).
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Oliwol\Slugify\HasSlug;
class Post extends Model
{
use HasSlug;
/**
* Attribute used for generating the slug.
*/
public function getAttributeToCreateSlugFrom(): string
{
return 'name';
}
/**
* Use slug for route binding.
*/
public function getRouteKeyName(): string
{
return 'slug';
}
/**
* This package uses Laravel's getRouteKeyName to store the slug.
* If you are using a different column for your routes,
* use getAttributeToSaveSlugTo to store the slug.
*/
public function getAttributeToSaveSlugTo(): string
{
return 'slug';
}
/**
* Scope applied when checking for uniqueness.
*/
public function scopeSlugQuery(): Builder
{
return fn (Builder $query): Builder => $query->where('tenant_id', 1);
}
}
Make sure your table contains the slug column:
$table->string('slug')->unique();
If you use scoping, you probably don’t want a global unique index. Example: slugs must be unique per tenant:
$table->unique(['tenant_id', 'slug']);
⚙️ How it works
The HasSlug trait hooks into the Eloquent saving event:
protected static function bootHasSlug(): void
{
static::saving(function (Model $model): void {
if ($model->isSluggable()) {
$model->createSlug();
}
});
}
When triggered, it will:
- Resolve the source attribute — from the
#[Slugify]attribute or agetAttributeToCreateSlugFrom()override. - Generate a slug from that source attribute.
- Skip regeneration if:
- The source attribute is not dirty (unchanged), or
- The slug has been manually set and differs from the original.
- Ensure uniqueness by incrementing existing slugs (my-post, my-post-2, my-post-3, …).
✅ Best practices & caveats
- Ensure the route key column (
getRouteKeyName()) is present in your table and is not the primary key (unless intentionally designed). - If you manually set a slug, the trait will not override it. Use this to allow user-edited slugs.
🔍 Custom Scoping Example
To ensure slugs are unique per tenant, override the scopeSlugQuery() method:
public function scopeSlugQuery(): Builder
{
return fn (Builder $query): Builder => $query->where('tenant_id', 1);
}
This will append a WHERE tenant_id = ? clause when checking for existing slugs.
📄 License
This package is open-sourced software licensed under the MIT license.