---
title: "Per-locale slugs — Translatable for Laravel | Roundly"
description: "Per-locale slugs via sluggable-for-laravel — two traits, two interfaces, no cast — sharing the same locale source of truth."
url: https://roundly-consulting.com/open-source/docs/translatable-for-laravel/slugs
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Translatable for Laravel](https://roundly-consulting.com/open-source/docs/translatable-for-laravel.md)

# Per-locale slugs

Slugs are owned by sluggable-for-laravel, installed with this package. A translatable model gets per-locale slugs with two traits and two interfaces — no cast, no glue:

```php
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Sluggable\Concerns\HasSlug;
use RoundlyConsulting\Sluggable\Contracts\Sluggable;
use RoundlyConsulting\Sluggable\Definitions\SlugDefinition;
use RoundlyConsulting\Sluggable\Definitions\SlugOptions;
use RoundlyConsulting\Translatable\Concerns\HasTranslations;
use RoundlyConsulting\Translatable\Contracts\Translatable;

final class Topic extends Model implements Translatable, Sluggable
{
    use HasTranslations;
    use HasSlug;

    protected $fillable = ['name', 'description'];

    /** @var list<string> */
    public array $translatable = ['name', 'description', 'slug'];

    public function slugOptions(): SlugOptions
    {
        return SlugOptions::make(
            SlugDefinition::for('slug')->from('name')->routeKey(),
        );
    }

    /** @return list<string> */
    protected function translationStatusExcludes(): array
    {
        return ['slug'];
    }
}

$topic = Topic::create(['name' => ['en' => 'Investing', 'sk' => 'Investovanie']]);
$topic->getTranslations('slug');   // ['en' => 'investing', 'sk' => 'investovanie']
```

## How it fits together

- Translatable extends sluggable’s ProvidesLocaleMaps, and HasTranslations implements it: isLocaleMapAttribute() → isTranslatableAttribute(), getLocaleMap() → getTranslations(), setLocaleMap() → setTranslations().
- Sluggable therefore detects the slug attribute as a locale map and builds the sk slug from the sk name regardless of the request locale.
- Every slug it writes goes through translatable’s locale-key validation and strict\_locales.
- The model must implement Translatable. A model using HasTranslations without the interface has no cast on its slug, so sluggable refuses it with InvalidSlugDefinitionException (localeMapContractMissing) rather than treating the jsonb column as a plain string.
- Slugs are generated for exactly the supported locales, through the TranslatableSlugLocales binding (see Configuration).

Uniqueness, engine-native unique indexes (SlugIndexes, php artisan sluggable:indexes), querying (whereSlug, whereSlugInAnyLocale, findBySlug), route binding, the UniqueSlug validation rule, slug history and redirects all belong to sluggable — see its documentation.

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/translatable-for-laravel.md)
