spatie/laravel-sluggable
Generate unique slugs for Eloquent models on create or update via a Sluggable attribute or HasSlug trait. Includes collision suffixes, translatable slugs, and self-healing URLs with 308 redirects.
Architecture fit
saving. It is a convenience layer over Str::slug() plus a uniqueness loop — not a content-addressing or routing framework. Fit is high for any Laravel app with public-facing, human-readable URLs (posts, pages, categories, products, tags).#[Sluggable] PHP attribute (declarative, no closures) and the HasSlug trait with getSlugOptions() (needed for scoped uniqueness, conditional skip, custom suffix generators). This is a governance decision, not a technical one — pick one per codebase or expect drift.getRouteKeyName/resolveRouteBinding and emits 308 redirects. That touches routing, caching, and SEO — treat it as a separate approval, not a bundled freebie.Integration feasibility
composer require), no service, no migration generator, no config publish required for the basic path. A competent Laravel developer can pilot on one model in under a day.slug, backfill via chunked job or artisan command, dedupe, then add a unique index. That migration sequence must be explicit — adding the unique index before backfill will fail on large tables.Technical risk
-1, -2, …). Under concurrent writes two requests can compute the same slug. A database unique index plus retry-on-conflict is mandatory; without it this becomes a data-integrity incident, not a bug report.LIKE wildcard handling (_, %, \) that silently reused suffixes on SQLite. Any org on multiple engines (SQLite in tests, Postgres/MySQL in prod) should test separator choices and confirm parity.HasSlug replaces Eloquent binding methods globally for the model. Interactions with nested/child bindings, policies, and any other package overriding the same methods need review. Self-healing parsing assumes the primary key is recoverable from the URL segment — non-integer or UUID keys are a design constraint to verify.Key questions
spatie/laravel-translatable v6+ already in the stack?308 correctly (including counting it as a redirect, not a 200)?upsert, or raw query builders touch these tables? Those bypass model events and will silently produce NULL slugs.Stack fit
illuminate/*. Optional second package only if translatable slugs are in scope.Migration path
Str::slug calls, model accessors, route model binding overrides) and consolidate before adopting, otherwise two competing mechanisms will produce inconsistent URLs.slug column, index after backfill, then unique constraint. Ship a deterministic backfill command that is idempotent and reports collisions.#[Sluggable] on one model; keep self-healing off.HasSlug + selfHealing: true) only on models where external links exist and 308 semantics are acceptable.Compatibility
skipGenerateWhen() now evaluated on every save, closure-only typing for option callbacks, removal of the $slugOptions cache, Sluggable facade renamed to SelfHealing, redirect default changed 301 → 308.Sequencing
Maintenance
Str::slug + uniqueness query) in a day if the package is ever abandoned. That caps long-term risk regardless of maintainer health.Support
Scaling
SELECT per save for the uniqueness check. Negligible for interactive writes; material for large imports. Route bulk ingestion through chunked jobs that respect model events, or generate slugs in a post-import pass.Failure modes
insert()/upsert()/seeders/mass updates that bypass Eloquent events → 404s discovered in production.Ramp-up
How can I help you explore Laravel packages today?