NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Sluggable for Laravel

Adoption & limitations

With the default options every generated slug matches Str::slug() for ordinary input of up to 255 characters, so switching an existing model keeps its URLs. Typical options map like this:

Typical option elsewhereSluggable
Generate from name into slugZero config, or #[Slug('slug', from: 'name')]
Regenerate on update->onUpdate(UpdatePolicy::WhenSourceChanges) (or keep IfEmpty + ->keepHistory())
Suffix starts at -1->sequentialSuffix(start: 1)
Global uniqueness vs a scoped DB index->uniqueWithin('clinic_id')
Manual getRouteKeyName()->routeKey()
“Don’t regenerate on update” / “prevent overwrite”->immutable() / ManualSlugPolicy

Run php artisan sluggable:duplicates before switching on a unique index, then sluggable:regenerate --mode=missing to fill any empty slugs.

Composing with other traits

  • HasSlug overrides performInsert, performUpdate, getRouteKeyName, getRouteKey, resolveRouteBindingQuery, resolveRouteBinding, resolveSoftDeletableRouteBinding and resolveChildRouteBinding — a trait overriding the same methods needs insteadof.
  • A method declared on the model class itself silently wins over the trait.
  • Helpers are prefixed sluggable…, and HasSlug declares none of currentLocale, fallbackLocale, readMap, writeMap, getTranslation(s) or setTranslation(s), so it composes with the common translation traits without insteadof.
  • Hooks run in creating/updating (not saving), after trait listeners declared earlier — declare a trait that sets a scope column (e.g. a tenant id) before HasSlug.

Limitations

  • Query-builder insert()/upsert(), saveQuietly() and withoutEvents() fire no model events and so generate nothing — call Slugs::apply($model) first.
  • A no-op save() does not back-fill (Laravel fires updating only for dirty models) — use regenerateSlugs(), Slugs::model(X::class)->regenerate() or sluggable:regenerate --mode=missing.
  • A race retry re-fires creating/updating for your listeners too; use retries(0) for models with non-idempotent listeners.
  • Across-locales and closure-scoped uniqueness are application-level only.
  • One history key_type per host.
  • Language-switch redirects and sitemap helpers are not included — listen for SlugChanged to drive your own.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.