Skip to content

Configuration

Via #[Slugify] Attribute

The #[Slugify] attribute accepts the following parameters:

ParameterTypeDefaultDescription
fromstring|array(required)Source attribute(s) or method name
tostring|nullgetRouteKeyName()Column to save the slug to
separatorstring|null'-'Word separator character
maxLengthint|nullnullMaximum slug length (truncates at word boundaries)
regenerateOnUpdatebooltrueWhether to regenerate on source change

Examples

php
// Full configuration
#[Slugify(from: 'name', to: 'slug')]
class Post extends Model
{
    use HasSlug;
}

// Only 'from' — slug column determined by getRouteKeyName()
#[Slugify(from: 'name')]
class Post extends Model
{
    use HasSlug;

    public function getRouteKeyName(): string
    {
        return 'slug';
    }
}

// Multiple source attributes
#[Slugify(from: ['first_name', 'last_name'], to: 'slug')]
class Author extends Model
{
    use HasSlug;
}
// first_name: "John", last_name: "Doe" → "john-doe"

// Custom separator
#[Slugify(from: 'title', to: 'slug', separator: '_')]
class Post extends Model
{
    use HasSlug;
}
// "Hello World" → "hello_world"

// Method source for complex slug generation
#[Slugify(from: 'getFullTitle', to: 'slug')]
class Post extends Model
{
    use HasSlug;

    public function getFullTitle(): string
    {
        return $this->category->name . ' ' . $this->title;
    }
}
// "Tech Laravel Tips" → "tech-laravel-tips"

// SEO-safe — slug only generated on creation
#[Slugify(from: 'title', to: 'slug', regenerateOnUpdate: false)]
class Post extends Model
{
    use HasSlug;
}

WARNING

The to parameter only controls where the slug is saved. For route model binding, you still need to override getRouteKeyName() separately on your model.

Via Method Overrides

Alternatively, configure slug generation by overriding methods:

MethodReturnsDescription
getAttributeToCreateSlugFrom()string|arraySource attribute(s)
getRouteKeyName()stringSlug column for route binding
getAttributeToSaveSlugTo()stringColumn to save slug (if different from route key)
getSlugSeparator()stringSeparator character (default '-')
getMaxSlugLength()?intMax length, truncated at word boundaries
shouldRegenerateSlugOnUpdate()boolWhether to regenerate on update (default true)
getSlugLanguage()stringLanguage for transliteration (default 'en')
scopeSlugQuery()BuilderScope for uniqueness checks
php
use Oliwol\Slugify\HasSlug;

class Post extends Model
{
    use HasSlug;

    public function getAttributeToCreateSlugFrom(): string|array
    {
        return 'name';
    }

    public function getRouteKeyName(): string
    {
        return 'slug';
    }

    public function getAttributeToSaveSlugTo(): string
    {
        return 'slug';
    }

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

Via the fluent SlugConfig API

For configurations that need a closure or conditional logic — things a PHP attribute cannot express — return a SlugConfig from a slugConfig() method:

php
use Oliwol\Slugify\HasSlug;
use Oliwol\Slugify\SlugConfig;

class Post extends Model
{
    use HasSlug;

    public function slugConfig(): SlugConfig
    {
        return SlugConfig::create()
            ->from(fn (self $post): string => $post->category->name.' '.$post->title)
            ->to('slug')
            ->separator('-')
            ->maxLength(60)
            ->regenerateOnUpdate(false);
    }
}
MethodArgumentDescription
from()string|array|ClosureSource attribute(s), method name, or a closure receiving the model
to()stringColumn to save the slug to
separator()stringSeparator character (default '-')
maxLength()intMax length, truncated at word boundaries
regenerateOnUpdate()boolWhether to regenerate on update (default true)
routeBinding()boolUse the slug column for route binding (requires to())
appendId()boolAnchor the route key to the primary key ({slug}-{id}) — see ID-Anchored Slugs

The from() closure receives the model instance and is the only way to combine related-model data or apply custom logic, since attributes cannot hold callables. As with method sources, a closure source skips dirty detection — the slug is regenerated on every save unless regenerateOnUpdate(false) is set.

When to use which

The #[Slugify] attribute handles most cases with zero boilerplate. Reach for SlugConfig only when you need a closure, conditional configuration, or a fluent chain.

Configuration Priority

When more than one configuration source is present, they are resolved in this order:

  1. Method overrides (e.g. getAttributeToCreateSlugFrom()) — highest precedence
  2. slugConfig() returning a SlugConfig
  3. #[Slugify] attribute
  4. Otherwise a LogicException is thrown

Released under the MIT License.