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

Artisan commands

php artisan sluggable:regenerate "App\Models\Product"                          # backfill empty slugs
php artisan sluggable:regenerate "App\Models\Topic" --locale=de                # backfill a new locale
php artisan sluggable:regenerate "App\Models\Product" --mode=stale --history --dry-run
php artisan sluggable:regenerate "App\Models\Product" --mode=all --force --chunk=1000 --queue
php artisan sluggable:indexes "App\Models\Topic" --column=slug --dry-run
php artisan sluggable:duplicates "App\Models\Product" --column=slug --locale=sk

{model} accepts a class name or a morph alias; it must be an Eloquent model implementing Sluggable. Each command is also on the facade, for scheduled tasks, deploy scripts and your own commands:

use RoundlyConsulting\Sluggable\Enums\RegenerationMode;
use RoundlyConsulting\Sluggable\Facades\Slugs;

Slugs::model(Product::class)->regenerate(mode: RegenerationMode::Stale, withHistory: true, dryRun: true);
Slugs::model(Product::class)->queueRegeneration(mode: RegenerationMode::All, chunk: 1000, force: true);
Slugs::model(Topic::class)->indexes(dryRun: true);
Slugs::model(Product::class)->duplicates(column: 'slug', locale: 'sk');

sluggable:regenerate

OptionEffect
--mode=missingDefault. Fill empty slugs and missing locales.
--mode=staleRecompute; replace only values whose base changed (no churn), and drop a suffix whose bare base is free again.
--mode=allRecompute everything; asks for confirmation unless --force.
--column, --localeRestrict the run; --locale=de backfills a new locale.
--chunkRows per chunk (1–10000, default 500).
--dry-runPrint up to 20 changes (key, column, locale, old → new); write nothing.
--historyRecord retired values even for definitions without keepHistory().
--without-eventsDo not dispatch SlugChanged.
--forceBypass locks (lockWhen / locked) and the confirmation.
--queueDispatch one RegenerateSlugsJob per chunk of keys.

Rows are scanned without global scopes (trashed and scoped-away rows included), chunked by key, and each changed row is saved normally — history and events as usual. Locks are respected unless --force. The queued job serialises ids and options, never models. SlugsRegenerated fires at the end of each run or queued chunk, but not on dry runs.

sluggable:indexes

Runs SlugIndexes::forModel() for the model and prints the DDL, the indexes that already exist and a summary. Run it after adding a locale to create that locale’s indexes, and with --dry-run to preview. Definition errors, such as colliding locales, fail the command.

sluggable:duplicates

Lists values a unique index would reject — grouped by scope values, locale and slug, without global scopes — plus values longer than maxLength; a notUnique() column gets no index, so only its over-long values are listed. Run it before adding an index to existing data. It only reports: it never changes data and exits with 0 either way.

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.