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

Sluggable Laravel Package

italofantone/sluggable

A lightweight Laravel package that automatically generates unique, SEO-friendly slugs for your Eloquent models. Add the trait, point it at a source column, and Sluggable handles slug creation and updates for you, avoiding duplicates with sensible defaults and simple configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Require the package and let Laravel discover it; publish any config with php artisan vendor:publish if the package ships one, then clear cached config (php artisan config:clear) so edits take effect.
  2. Add the storage column first: a slug string column, indexed and preferably unique(), in the migration for each model that needs one. Decide nullable vs NOT NULL before you have data.
  3. Wire the package's model concern/hook into the model, point it at the source attribute (e.g. title), and save a record through Eloquent — not a raw insert — to confirm a slug appears.
  4. Where to look first: the slug generation path itself. v2.1.0 refactored it, so read that class before anything else, then the updated slug tests — the test suite is the clearest executable spec of the new behavior for edge cases like punctuation, casing, truncation, and collisions.
  5. First real use case: a Post/Product model with a title-derived slug plus route model binding. Build that end to end (migration → trait → save → Route::get('/posts/{post:slug}')) before touching config knobs.

Implementation Patterns

  • Generate on create, not on every save. Derive the slug once from the source field and keep it stable; regenerate only when the source changes and only when you deliberately want the URL to move.
  • Uniqueness via database + package. Rely on the unique index as the source of truth and let the package's collision handling (numeric or random suffix) produce the candidate. Catch the integrity-constraint violation and retry for concurrent writes.
  • Backfill as a first-class task. Because v2.1.0 changed the generation logic, write an artisan command that computes the new slug for existing rows, reports a diff, and applies it only for rows whose output actually changes. Run it in dry-run mode before deploying.
  • Route binding by slug, with a fallback. Bind on the slug column, but keep an ID-based fallback route or a resolveRouteBinding that accepts either, so old links and admin tooling don't 404.
  • Customization through the package's extension point, not by editing vendor files: transliteration rules, separator, max length, stop words, and the source field per model.
  • Multi-column / localized slugs. Store per-locale slugs in a JSON column or a related table, and keep a single canonical slug for routing.
  • Test the contract, not the implementation. Feature tests asserting "title in → slug out" for a handful of awkward titles (unicode, emoji, trailing punctuation, all-symbol, 200+ chars, duplicates) will catch future generation refactors far better than mocking the generator.

Gotchas and Tips

  • v2.1.0 is a refactor, so treat slug output as potentially changed. It is not marketed as breaking, but slugs are persisted public identifiers — different transliteration, casing, separators, or truncation is a behavioral change even when the API is identical. Diff a sample of existing rows against the new generator before shipping.
  • Anything that caches URLs (route cache, sitemap, CDN, search index, outbound links in emails) holds the old slugs. Plan redirects for changed values.
  • Transliteration depends on environment. intl/iconv availability differs between local, CI, and production containers — verify slugs generated on the server, not just your machine.
  • Truncation and suffix ordering interact badly: a long title truncated at the max length can leave no room for the uniqueness suffix. Check the package's configured limit against your column length.
  • Model events are bypassed by bulk inserts, DB::table() updates, seeders, and some queue/import paths. Generate slugs explicitly there or route writes through Eloquent.
  • Don't regenerate on every save. It silently rewrites history and breaks inbound links and SEO; gate regeneration behind an explicit flag or an admin action.
  • Soft deletes keep rows in the table, so a unique index still blocks reusing a slug from a trashed record — decide whether to free slugs on delete or accept the constraint.
  • Debugging: log the raw source string and the generated slug side by side; most "wrong slug" reports are an unexpected source value, stale config cache, or a model saved before the trait was added.
  • Extension point preference: bind a custom generator/adapter in a service provider over subclassing internals, so the next refactor doesn't break you.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core