Weave 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

Slugifier Laravel Package

keyvanakbary/slugifier

keyvanakbary/slugifier is a lightweight PHP library for turning text into clean, URL-friendly slugs. It supports UTF-8 transliteration, customizable separators and transformations, and works standalone or in Laravel via Composer.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a deliberately tiny, framework-agnostic library: one class, no service provider, no config file. The fastest path is to read the single source file before wiring anything up, because the repo metadata here is stale (last release 2015) and the API surface is small enough to verify in a minute.

  1. Install it:

    composer require keyvanakbary/slugifier
    
  2. Open vendor/keyvanakbary/slugifier/src/ and read the Slugifier class. That is the whole package — confirm the constructor signature and whether slugify() is an instance or static call in your installed version before writing call sites.

  3. Use it directly for the first slug:

    use Keyvanakbary\Slugifier\Slugifier;
    
    $slugifier = new Slugifier();
    $slug = $slugifier->slugify('Hello, Laravel World!'); // "hello-laravel-world"
    
  4. First real use case: give a Post model a slug column and generate it from the title when the model is created or when the title changes.

    // database migration
    $table->string('slug')->unique();
    

Because it is pure and stateless, you can instantiate it anywhere — a controller, an observer, a queued job, a factory. There is no bootstrapping and nothing to register.

Implementation Patterns

Wrap it in a small application service. Don't sprinkle new Slugifier() through controllers. Create an app-level SlugService that owns the slugifier and the uniqueness policy. This gives you one place to swap in a different library later and one place to test.

namespace App\Support;

use Keyvanakbary\Slugifier\Slugifier;

class SlugService
{
    public function __construct(private Slugifier $slugifier) {}

    public function make(string $value): string
    {
        return $this->slugifier->slugify($value);
    }
}

Bind it as a singleton in AppServiceProvider — safe because the underlying object holds no state.

$this->app->singleton(SlugService::class, fn () => new SlugService(new Slugifier()));

Auto-populate on the model. A booted() hook keeps slugs consistent regardless of whether the record is created from a controller, a factory, or an importer:

protected static function booted(): void
{
    static::saving(function (self $post) {
        if ($post->isDirty('title') || blank($post->slug)) {
            $post->slug = app(SlugService::class)->unique($post->title, $post->id);
        }
    });
}

Handle uniqueness yourself. The library only transforms text; it has no concept of collisions. Use a bounded loop rather than recursing forever:

public function unique(string $value, ?int $ignoreId = null): string
{
    $base = $this->make($value);
    $slug = $base;
    $i = 2;

    while (Post::where('slug', $slug)->when($ignoreId, fn ($q) => $q->where('id', '!=', $ignoreId))->exists()) {
        $slug = "{$base}-{$i}";
        $i++;
        if ($i > 100) { throw new \RuntimeException("Cannot slugify: {$value}"); }
    }

    return $slug;
}

Form requests. If you accept a user-supplied slug, normalise it in prepareForValidation() so the rest of the request lifecycle only ever sees clean input:

protected function prepareForValidation(): void
{
    if ($this->filled('slug')) {
        $this->merge(['slug' => app(SlugService::class)->make($this->input('slug'))]);
    }
}

Route model binding. Once the column exists, point binding at it and keep IDs out of URLs:

public function getRouteKeyName(): string { return 'slug'; }

Seeders, imports, and backfills. The pure-function nature makes it ideal for batch jobs and one-off artisan commands — no locale or container state to worry about, so a chunkById backfill is trivial.

Defensive wrapper for fallbacks. Slugs can come out empty for input that is entirely punctuation or non-Latin. Decide the policy once in your service (e.g. fall back to Str::random(8)) rather than at each call site.

Gotchas and Tips

  • It is a 2015 release. Check PHP 8 compatibility before depending on it in a modern Laravel app — a quick php -l plus a smoke test in a test case will tell you. If it trips deprecation notices, the fix is usually a one-line patch, but that means forking or replacing it.
  • No Laravel integration ships with it. No facade, no config, no provider. Everything above is glue you write; treat that as a feature (tiny dependency) rather than a gap.
  • Transliteration is basic. It will not reliably handle CJK, Cyrillic, Arabic, or emoji. If your titles are multilingual, pair it with an explicit transliteration step or choose a different slugifier; don't assume it "just works" for non-ASCII input.
  • No length enforcement. slug columns are usually VARCHAR(255) and unique. Truncate the base before appending the -2, -3 suffix, or you will hit index-length errors on long titles, especially with MySQL utf8mb4.
  • Empty input edge case. Strings that reduce to nothing will yield an empty slug. Guard with a fallback in your service — an empty unique index value is a silent collision source.
  • Be careful when re-slugging. Changing slugs on every save silently breaks inbound links. Only regenerate when the title actually changed (isDirty('title')) or the slug is blank, and consider keeping old slugs in a redirect table.
  • Debugging is easy but opaque. There is no exception on "bad" output; it just returns a string. When a slug looks wrong, var_dump the raw input first — most surprises are encoding issues (non-UTF-8 source data) rather than the slugifier.
  • Keep call sites behind your wrapper. Since extension points in the package itself are minimal, the wrapper is your extension point. That is what lets you later swap in Str::slug() or cocur/slugify without touching models or controllers.
  • Test the boundary cases once. Add a small unit test covering punctuation, accents, whitespace runs, and long input. It documents the actual behaviour of this specific version, which is worth more than the README given the package's age.
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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment