The Slugs facade
The Slugs facade is the one entry point for everything that is not a model hook: flat verbs for text and a single model, and Slugs::model(Product::class) — a class name or its morph alias — for class-wide backfills, audits, indexes and history lookups:
use RoundlyConsulting\Sluggable\Enums\RegenerationMode;
use RoundlyConsulting\Sluggable\Facades\Slugs;
// Text and one model
Slugs::slugify('Žltý kôň @ home', language: 'sk'); // 'zlty-kon-at-home'
Slugs::generate($product, column: 'slug'); // the value it would get; sets nothing
Slugs::apply($product); // run the save-time pass now (before saveQuietly/imports)
Slugs::recompute($product, columns: ['slug']); // recompute from the sources; you save
Slugs::regenerate($product, columns: ['slug']); // recompute + save
Slugs::withoutGeneration(fn () => $importer->run()); // nestable, exception-safe toggles
Slugs::unlocked(fn () => $product->update(['slug' => 'new']));
Slugs::locales(); // SlugLocales
// A whole model class (a class name or its morph alias)
$products = Slugs::model(Product::class);
$products->regenerate(mode: RegenerationMode::Stale, dryRun: true); // RegenerationReport
$products->regenerate(mode: RegenerationMode::Missing, withHistory: true, columns: ['slug']);
$products->queueRegeneration(mode: RegenerationMode::All, chunk: 1000); // jobs dispatched (int)
$products->duplicates(column: 'slug', locale: 'sk'); // list<SlugDuplicate> a unique index would reject
$products->indexes(dryRun: true); // SlugIndexReport: the DDL, nothing executed
$products->indexes(); // create the missing engine-native indexes
$products->findInHistory('old-name', locale: 'sk'); // the row that retired a slug, or null
$products->options(); // ResolvedSlugOptionsFlat methods
| Method | Purpose |
|---|---|
slugify(string $text, ?string $language = null, ?string $separator = null): string | The pipeline with the configured defaults. |
generate(Model $model, ?string $column = null, ?string $locale = null): string | The unique value the model would get; sets nothing. |
apply(Model $model, ?array $columns = null, ?array $locales = null): SlugGenerationResult | Run the save-time pass now (before saveQuietly() or imports). |
recompute(Model $model, ?array $columns = null, ?array $locales = null): SlugGenerationResult | Recompute from the sources on the instance; you save. |
regenerate(Model $model, ?array $columns = null, ?array $locales = null): Model | recompute() + save(). |
model(string $model): ModelSlugs | Class-wide work for a class name or morph alias (below). |
withoutGeneration(Closure $callback): mixed | Hooks off (nestable, exception-safe). |
unlocked(Closure $callback): mixed | Bypass locks. |
isGenerationEnabled(): bool, isUnlocked(): bool | Toggle state. |
locales(): SlugLocales | The bound locale source. |
Class-wide: Slugs::model()
| Method | Purpose |
|---|---|
regenerate(mode, dryRun, withHistory, columns, locales, chunk, withoutEvents, force): RegenerationReport | Backfill or recompute the whole class — the same options as sluggable:regenerate. |
queueRegeneration(mode, withHistory, columns, locales, chunk, withoutEvents, force): int | Dispatch one RegenerateSlugsJob per chunk; returns the job count. |
duplicates(?string $column = null, ?string $locale = null): array | list<SlugDuplicate> a unique index would reject. |
indexes(bool $dryRun = false, ?array $columns = null): SlugIndexReport | Create the missing engine-native indexes, or only plan them. |
findInHistory(string $slug, ?string $column = null, ?string $locale = null, Builder|Relation|null $within = null): ?Model | The row that retired a slug, through the default or the given query. |
options(): ResolvedSlugOptions | The resolved definitions. |
- regenerate() and queueRegeneration() scan rows without global scopes; each changed row is saved normally, so history and events apply. mode takes a RegenerationMode — Missing (default), Stale or All.
- findInHistory() resolves through the model’s default query (global scopes applied) or the query you pass as within — a retired slug never reveals a row that query would not return.
- Each Artisan command is the same call: sluggable:regenerate, :indexes and :duplicates run model()->regenerate() / queueRegeneration(), indexes() and duplicates().
The toggles are depth counters released in finally blocks — nestable, exception-safe and never left set between Octane requests. On a model, $model->regenerateSlugs() is Slugs::recompute($model) from the model side: it recomputes on the instance and leaves saving to you; Slugs::regenerate() does both.
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 cryptoBy 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.