javiereguiluz/easyslugger
EasySlugger is a lightweight PHP library for generating clean, URL-friendly slugs from any text. It handles UTF-8 and transliterates accented and non-Latin characters, and can produce unique slugs for database records. Simple static API with no framework dependencies.
EasySlugger is a tiny, dependency-free PHP library — not a Laravel package. There is no service provider, no facade, and no auto-discovery. You install it and call static methods.
composer require javiereguiluz/easyslugger
The first thing to understand is that it ships three slugger classes, and picking the wrong one is the most common early mistake:
| Class | Behavior | Use when |
|---|---|---|
EasySlugger\Slugger |
Basic ASCII-oriented slugging | Input is already Latin/ASCII |
EasySlugger\Utf8Slugger |
Full UTF-8 transliteration (á → a, ñ → n, ç → c, etc.) |
Any user-supplied or multilingual text |
EasySlugger\SeoSlugger |
SEO-tuned variant | You want search-engine-oriented output |
The API is a single static call with an optional separator:
use EasySlugger\Utf8Slugger;
$slug = Utf8Slugger::slugify('Héllo, Wörld! Año 2024');
// "hello-world-ano-2024"
First use case: generating a slug from a title on your Post model.
$post = Post::create([
'title' => $request->title,
'slug' => Utf8Slugger::slugify($request->title),
]);
Look first at the library's own source (src/) — it is small enough to read end-to-end in one sitting, and doing so will tell you exactly which characters each slugger handles. Do not assume behavior parity with Laravel's Str::slug(); verify.
Because the library is static and stateless, wrap it once so you can swap the implementation later (and so tests can mock it).
trait HasSlug
{
public static function bootHasSlug(): void
{
static::saving(function ($model) {
if (empty($model->slug)) {
$model->slug = Utf8Slugger::slugify($model->{$model->slugSource()});
}
});
}
protected function slugSource(): string
{
return 'title';
}
}
The library does not check the database. Day-to-day you will want a suffix strategy:
public static function uniqueSlug(string $source, string $column = 'slug'): string
{
$base = Utf8Slugger::slugify($source);
$base = $base ?: 'item'; // guard against empty output
$slug = $base;
$i = 2;
while (static::query()->where($column, $slug)->exists()) {
$slug = "{$base}-{$i}";
$i++;
}
return $slug;
}
For high-concurrency writes, do this inside a DB::transaction() with a unique index on the column and retry on constraint violation — the naive loop races.
Once slugs exist, use them in URLs.
public function getRouteKeyName(): string
{
return 'slug';
}
Keep the slug column unique() and index(); you will hit it on every route resolution.
For existing records, a one-off command with chunking is the standard pattern:
Post::whereNull('slug')->chunkById(500, function ($posts) {
foreach ($posts as $post) {
$post->update(['slug' => Post::uniqueSlug($post->title)]);
}
});
This is where EasySlugger shines — it is genuinely fast in tight loops. For imports, generate all slugs in memory first, then upsert, rather than saving row-by-row and running the uniqueness query each time.
If you want deterministic slugs from a normalized source, do the slugification in prepareForValidation() or a DTO, and treat the model as a plain consumer. That keeps model events dumb and testable.
NOASSERTION. Before shipping to production, confirm the actual license text in the repository (historically MIT) with your legal/compliance process.utf8_* helpers if used internally).EasySlugger to appear magically.Str::slug(). Since Laravel 6+, Str::slug() uses voku/portable-ascii and handles UTF-8 well. For new projects, benchmark against Str::slug() before adding a dependency. EasySlugger's argument is raw speed and a small footprint, not capability.Slugger on accented input produces very different (often stripped) results than Utf8Slugger. Always test with the exact input language of your users.''. Always coalesce to a fallback.-, _) to avoid surprises with repeated-consecutive handling.Str::limit) or a custom truncation if URL aesthetics matter.app/Support namespace rather than patching vendor code.How can I help you explore Laravel packages today?