Validation
use Illuminate\Validation\Rule;
use RoundlyConsulting\Sluggable\Rules\UniqueSlug;
use RoundlyConsulting\Sluggable\Rules\ValidSlug;
$request->validate([
'slug' => ['nullable', 'string', ValidSlug::for(Product::class),
UniqueSlug::for(Product::class)->ignore($product)->where('clinic_id', $clinicId)],
]);
'slug' => ['nullable', 'array', UniqueSlug::for(Topic::class)->ignore($topic)], // every locale
'slug' => [Rule::uniqueSlug(Topic::class, 'slug')->locale('sk')],
'slug' => [UniqueSlug::for(Product::class)->within($clinic)], // scope from a model
'slug' => [UniqueSlug::for(Product::class)->scopeFromInput('clinic_id')], // opt-in: scope from input
'slug' => [new ValidSlug(separator: '-', maxLength: 255, unicode: false, lowercase: true, reserved: [])],UniqueSlug
- Uses exactly the generation semantics — scopes, trashed rows, no global scopes, reserved words — and compares the normalised value under the Normalize policy.
- ignore() skips the record being edited (a model or a key); locale('sk') checks one locale only.
- Scope values are never read from the request implicitly — that would be a cross-tenant “is this slug taken?” oracle. Per scope column: where(), then the within() / ignore($model) model’s attribute, then — only for columns named in scopeFromInput() — the validated data; otherwise the rule throws missingScopeValue.
- Locale-map input: a string means the current (or locale()) locale; arrays check every supported locale; unsupported locales are ignored; errors land on slug.{locale}.
ValidSlug
ValidSlug passes when the value equals its own normalisation, fits maxLength and is not reserved. ValidSlug::for(Model::class) uses the definition’s format and reserved words; the constructor takes them explicitly, as in the last example above.
Rule macros
Rule::uniqueSlug(Model::class, $column) and Rule::validSlug(Model::class, $column) return the same rules and are known to PHPStan through the package extension.
Messages
| Key | Default message |
|---|---|
sluggable::validation.unique | The :attribute has already been taken. |
sluggable::validation.unique_locale | The :attribute (:locale) has already been taken. |
sluggable::validation.format | The :attribute must be a valid slug (lowercase letters, numbers and separators). |
sluggable::validation.too_long | The :attribute may not be longer than :max characters. |
sluggable::validation.reserved | The :attribute is reserved and cannot be used. |
sluggable::validation.locked | The :attribute can no longer be changed. |
Publish them with --tag=sluggable-translations to change or translate the wording.
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.