Skip to content

API Reference

#[Slugify] Attribute

php
#[Slugify(
    from: 'title',            // string|array — required
    to: 'slug',               // ?string — default: getRouteKeyName()
    separator: '-',           // ?string — default: '-'
    maxLength: null,          // ?int — default: null (no limit)
    regenerateOnUpdate: true, // bool — default: true
    routeBinding: false,      // bool — default: false
    appendId: false,          // bool — default: false
)]

When routeBinding: true is set (and to is specified), getRouteKeyName() returns the slug column so Laravel resolves route parameters by slug automatically.

When appendId: true is set, the route key becomes {slug}-{id} and the model resolves by the trailing ID, with a 308 canonical redirect for stale slugs. Requires the HasSlug trait. See ID-Anchored Slugs.

SlugConfig

A fluent, mutable builder returned from a slugConfig() method on the model as an alternative to the #[Slugify] attribute. Use it when you need a closure source or conditional logic the attribute can't express. See Configuration.

php
use Oliwol\Slugify\SlugConfig;

SlugConfig::create()
    ->from('title')          // string|array|Closure — closure receives the model
    ->to('slug')             // string
    ->separator('-')         // string
    ->maxLength(60)          // int
    ->regenerateOnUpdate()   // bool — default true
    ->routeBinding()         // bool — default true when called
    ->appendId();            // bool — default true when called
Builder methodGetter
from(string|array|Closure)getFrom(): string|array|Closure
to(string)getTo(): ?string
separator(string)getSeparator(): ?string
maxLength(int)getMaxLength(): ?int
regenerateOnUpdate(bool = true)shouldRegenerateOnUpdate(): bool
routeBinding(bool = true)usesRouteBinding(): bool
appendId(bool = true)usesIdAnchoring(): bool

SlugConfig::fromAttribute(Slugify $attribute): self builds a config from an attribute — used internally to normalize both configuration sources.

HasSlug Trait

Static Methods

findBySlug(string $slug): ?static

Find a model by its slug. Returns null if not found.

php
$post = Post::findBySlug('hello-world');

findBySlugOrFail(string $slug): static

Find a model by its slug or throw ModelNotFoundException.

php
$post = Post::findBySlugOrFail('hello-world');

Instance Methods

createSlug(): void

Generate and set the slug on the model. Called automatically during the saving event.

isSluggable(): bool

Determine if the model can have a slug generated. Returns false if:

  • The slug target column equals the primary key
  • No source attribute has a filled string value

slugify(string $toSlug): string

Convert a string to a slug using the configured separator and language.

php
$model->slugify('Hello World'); // "hello-world"

incrementSlugIfExists(string $slug): string

Ensure the slug is unique by appending an incrementing suffix if needed.

truncateSlug(string $slug, int $reservedLength = 0): string

Truncate the slug at word boundaries to fit within the max length.

getRouteKeyName(): string

Return the route key name. When routeBinding: true is configured and to is set, returns the slug column. Otherwise delegates to the model's primary key.

shouldUseSlugForRouteBinding(): bool

Return whether the model is configured to use the slug column for route model binding.

isIdAnchored(): bool

Return whether ID-anchored slugs (appendId) are enabled for this model.

getRouteKey()

When appendId is enabled, returns {slug}-{id}. Otherwise returns the standard Eloquent route key (getAttribute(getRouteKeyName())).

resolveRouteBindingQuery($query, $value, $field = null)

When appendId is enabled and no explicit field is given, resolves the model by the trailing ID extracted from the route value. Otherwise behaves like the default Eloquent binding query.

Overridable Methods

slugConfig(): SlugConfig

Optional. Return a SlugConfig to configure slug generation fluently — takes precedence over the #[Slugify] attribute. Define this method when you need closures or conditional logic.

getAttributeToCreateSlugFrom(): string|array|Closure

Return the source attribute name(s), method name, or a closure. Overriding this takes precedence over both slugConfig() and the #[Slugify] attribute.

getAttributeToSaveSlugTo(): string

Return the column name where the slug is saved. Defaults to getRouteKeyName().

getSlugSeparator(): string

Return the separator character. Default: '-'.

getMaxSlugLength(): ?int

Return the maximum slug length. Default: null (no limit).

shouldRegenerateSlugOnUpdate(): bool

Return whether the slug should regenerate on source attribute change. Default: true.

shouldUseSlugForRouteBinding(): bool

Return whether route model binding should use the slug column. Default: derived from routeBinding attribute parameter. Override to use dynamic logic.

php
public function shouldUseSlugForRouteBinding(): bool
{
    return true;
}

getSlugLanguage(): string

Return the language for transliteration. Default: 'en'.

scopeSlugQuery($query)

Apply custom scoping to uniqueness checks and findBySlug() queries.

php
public function scopeSlugQuery($query)
{
    return $query->where('tenant_id', $this->tenant_id);
}

HasSlugHistory Trait

Requires the slug_history table. Publish with:

bash
php artisan vendor:publish --tag=slugify-migrations

Static Methods

findBySlugWithHistory(string $slug): ?static

Find a model by its current slug or any historical slug.

Relationships

slugHistory(): MorphMany

Access all previous slugs:

php
$post->slugHistory; // Collection of SlugHistory entries

HasTranslatableSlug Trait

Requires spatie/laravel-translatable.

Static Methods

findBySlug(string $slug, ?string $locale = null): ?static

Find a model by its translated slug. Defaults to app()->getLocale().

php
Post::findBySlug('hallo-welt', 'de');
Post::findBySlug('hello-world'); // uses current app locale

withSlug() Factory Macro

A macro registered on Illuminate\Database\Eloquent\Factories\Factory that controls slug generation in test factories.

php
Factory::withSlug(?string $slug = null): static

Available on all factories for models that use HasSlug. Registered automatically by the service provider.

Parameters

ParameterTypeDescription
$slug?stringOptional custom slug. If omitted, the slug is generated from the configured source attribute.

Examples

php
// Unsaved model with auto-generated slug
Post::factory()->withSlug()->make();

// Unsaved model with custom slug
Post::factory()->withSlug('my-slug')->make();

// Batch creation with unique slugs
Post::factory()->count(3)->withSlug()->create();
// → "hello-world", "hello-world-2", "hello-world-3"

Behaviour

  • For make(): generates the slug immediately without saving
  • For create(): lets the saving event handle uniqueness sequentially, producing correct incremented slugs in batch scenarios
  • Models without HasSlug are silently skipped

SlugRule

A validation rule for checking slug uniqueness in form requests.

php
use Oliwol\Slugify\Rules\SlugRule;

Constructor

new SlugRule(string $modelClass)

Create a new rule for the given model class.

php
new SlugRule(Post::class)

Static Methods

SlugRule::for(string $modelClass): static

Fluent alternative to the constructor.

php
SlugRule::for(Post::class)

Instance Methods

ignore(Model $model): static

Exclude a specific model from the uniqueness check. Use this for update scenarios so the model's own slug does not trigger a failure.

php
SlugRule::for(Post::class)->ignore($this->post)

scope(string $column, mixed $value): static

Add an extra WHERE constraint to the uniqueness check. Chainable.

php
SlugRule::for(Post::class)->scope('tenant_id', auth()->user()->tenant_id)

Behaviour

  • Uses the model's configured slug column (to / getAttributeToSaveSlugTo())
  • Calls scopeSlugQuery() automatically when the model defines it
  • Provides a translatable error message via slugify::validation.slug_unique

Examples

php
// Create
'slug' => ['required', 'string', new SlugRule(Post::class)]

// Update
'slug' => ['required', 'string', SlugRule::for(Post::class)->ignore($this->post)]

// Scoped create
'slug' => ['required', 'string', SlugRule::for(Post::class)->scope('tenant_id', auth()->user()->tenant_id)]

SlugFormat

A validation rule for checking that a value is a valid slug format.

php
use Oliwol\Slugify\Rules\SlugFormat;

Constructor

new SlugFormat(string $separator = '-')

Create a new format rule with the given separator (default: -).

Behaviour

Validates that the value:

  • Contains only lowercase letters (a–z), numbers (0–9), and the separator
  • Does not start or end with the separator
  • Does not contain consecutive separators

Fails for non-string values, empty strings, uppercase letters, or special characters.

Examples

php
// Default separator
'slug' => ['required', new SlugFormat()]

// Custom separator
'slug' => ['required', new SlugFormat(separator: '_')]

// Combined format + uniqueness check
'slug' => ['required', new SlugFormat(), new SlugRule(Post::class)]

SlugRedirectMiddleware

Automatically redirects requests using a historical slug to the current canonical URL. Registered under the alias slug.redirect.

Usage

php
Route::get('/posts/{post:slug}', PostController::class)
    ->middleware(['slug.redirect']);

Requires HasSlugHistory on the bound model and SubstituteBindings in the middleware stack (included in Laravel's default web group).

Behaviour

  • Compares the URL slug against the model's current slug after route model binding
  • If they differ, issues a redirect to the URL with the current slug
  • Preserves the query string on redirect
  • Passes through unchanged for models without HasSlugHistory

Configuration

The redirect status code defaults to 301 and can be changed by publishing the config:

bash
php artisan vendor:publish --tag=slugify-config
php
// config/slugify.php
return [
    'redirect_status' => 301,
];

Events

Oliwol\Slugify\Events\SlugGenerated

Dispatched when a slug is created for the first time.

PropertyTypeDescription
$modelModelThe Eloquent model
$slugstringThe generated slug

Oliwol\Slugify\Events\SlugUpdated

Dispatched when an existing slug changes to a new value.

PropertyTypeDescription
$modelModelThe Eloquent model
$oldSlugstringThe previous slug
$newSlugstringThe new slug

Artisan Command

slugify:generate

bash
php artisan slugify:generate {model} [--force] [--dry-run]
Argument/OptionDescription
modelFully qualified model class name
--forceOverwrite existing slugs
--dry-runPreview changes without saving

Released under the MIT License.