---
title: "Configuration — Sluggable for Laravel | Roundly"
description: "Every config key with its default and env variable — formatting, uniqueness, update policy, locales, limits, retries and history."
url: https://roundly-consulting.com/open-source/docs/sluggable-for-laravel/configuration
language: en
---

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

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

# Configuration

Every key has a working default, so most apps never publish the config. The published config/sluggable.php in full:

```php
return [
    'defaults' => [
        'column' => 'slug',
        'source' => 'name',
        'separator' => '-',
        'max_length' => 255,
        'max_words' => null,
        'language' => env('SLUGGABLE_LANGUAGE', 'en'),
        'dictionary' => ['@' => 'at'],
        'lowercase' => true,
        'unicode' => false,
        'uniqueness' => 'global',
        'locale_uniqueness' => 'per_locale',
        'include_trashed' => true,
        'on_create' => true,
        'on_update' => env('SLUGGABLE_ON_UPDATE', 'if_empty'),
        'manual' => 'normalize',
        'empty_source' => 'random',
        'suffix' => 'sequential',
        'suffix_start' => 2,
        'random_length' => 8,
        'target_locales' => 'source',
        'locale_fallback' => 'any',
    ],
    'reserved' => [],
    'locales' => [
        'supported' => null,
        'fallback' => env('SLUGGABLE_FALLBACK_LOCALE'),
    ],
    'binding' => [
        'key_fallback' => false,
    ],
    'limits' => [
        'max_source_length' => 2000,
        'sequential_probes' => 50,
        'probe_batch' => 10,
        'random_attempts' => 10,
    ],
    'concurrency' => [
        'retries' => env('SLUGGABLE_RETRIES', 3),
    ],
    'history' => [
        'enabled' => env('SLUGGABLE_HISTORY', false),
        'redirect' => env('SLUGGABLE_HISTORY_REDIRECT', true),
        'redirect_status' => 301,
        'avoid_reuse' => false,
        'table' => env('SLUGGABLE_HISTORY_TABLE', 'slug_history'),
        'model' => \RoundlyConsulting\Sluggable\Models\SlugHistory::class,
        'prune_after_days' => env('SLUGGABLE_HISTORY_PRUNE_DAYS'),
    ],
    'key_type' => env('SLUGGABLE_KEY_TYPE', 'bigint'),
];
```

## Every key

| Key | Default | Env | Purpose |
| --- | --- | --- | --- |
| `defaults.column` | slug | — | Column of the zero-config definition. |
| `defaults.source` | name | — | Source attribute of the zero-config definition. |
| `defaults.separator` | \- | — | 1–3 characters from - . \_ ~ |
| `defaults.max_length` | 255 | — | Characters incl. prefix/suffix and collision suffix (8–2048). |
| `defaults.max_words` | null | — | Word cap (null or blank = none). |
| `defaults.language` | en | `SLUGGABLE_LANGUAGE` | Transliteration language of string slugs (locale maps use each locale). |
| `defaults.dictionary` | \['@' => 'at'\] | — | Replacements applied before stripping. |
| `defaults.lowercase` | true | — | Lowercase the slug. |
| `defaults.unicode` | false | — | Keep non-ASCII letters instead of transliterating. |
| `defaults.uniqueness` | global | — | none / global / scoped |
| `defaults.locale_uniqueness` | per\_locale | — | per\_locale / across\_locales |
| `defaults.include_trashed` | true | — | Soft-deleted rows keep their slug reserved. |
| `defaults.on_create` | true | — | Generate on create. |
| `defaults.on_update` | if\_empty | `SLUGGABLE_ON_UPDATE` | never / if\_empty / when\_source\_changes / always |
| `defaults.manual` | normalize | — | normalize / verbatim / strict |
| `defaults.empty_source` | random | — | random / skip / fail |
| `defaults.suffix` | sequential | — | sequential / random (custom needs suffixUsing()). |
| `defaults.suffix_start` | 2 | — | First sequential suffix (1–1000). |
| `defaults.random_length` | 8 | — | Random slug / random suffix length (4–32). |
| `defaults.target_locales` | source | — | source / supported / current |
| `defaults.locale_fallback` | any | — | none / fallback / any — the reading and binding chain. |
| `reserved` | \[\] | — | Slugs nobody may take (case-insensitive). |
| `locales.supported` | null | — | Locale list; null = app.locale + app.fallback\_locale. |
| `locales.fallback` | null | `SLUGGABLE_FALLBACK_LOCALE` | null or blank = app.fallback\_locale. |
| `binding.key_fallback` | false | — | Try the primary key after a slug miss. |
| `limits.max_source_length` | 2000 | — | Source characters considered (64–100000). |
| `limits.sequential_probes` | 50 | — | Sequential candidates before random ones (1–1000). |
| `limits.probe_batch` | 10 | — | Candidates checked per query (1–100). |
| `limits.random_attempts` | 10 | — | Random candidates before giving up (1–100). |
| `concurrency.retries` | 3 | `SLUGGABLE_RETRIES` | Retries after a unique-index race (0–20; 0 disables). |
| `history.enabled` | false | `SLUGGABLE_HISTORY` | Default for keepHistory(). |
| `history.redirect` | true | `SLUGGABLE_HISTORY_REDIRECT` | Default for redirectFromHistory(). |
| `history.redirect_status` | 301 | — | 301 / 302 / 307 / 308 |
| `history.avoid_reuse` | false | — | Default for avoidHistoricalSlugs(). |
| `history.table` | slug\_history | `SLUGGABLE_HISTORY_TABLE` | History table (blank → slug\_history; must be a valid identifier). |
| `history.model` | SlugHistory::class | — | Swappable history model (must extend it; anything else throws). |
| `history.prune_after_days` | null | `SLUGGABLE_HISTORY_PRUNE_DAYS` | Prune age (1–36500); null or blank (SLUGGABLE\_HISTORY\_PRUNE\_DAYS=) keeps forever. |
| `key_type` | bigint | `SLUGGABLE_KEY_TYPE` | Primary-key type of the slugged models (bigint / uuid / ulid, case-insensitive); anything else throws InvalidConfigurationException. |

Every key is validated on first use: a typo or an out-of-range value (say SLUGGABLE\_HISTORY=disabled) throws InvalidSlugDefinitionException naming the key instead of silently falling back. Switches accept true/false, 1/0, on/off and yes/no; the string keys (column, source, separator, language, history.table, locales.fallback) must be strings; reserved and locales.supported must be lists of non-empty strings and dictionary a string ⇒ string map — a bad entry throws rather than being dropped. Only a key that is not set — absent, null or blank (a host’s KEY=) — takes its default. An unrecognised key\_type throws the toolkit’s InvalidConfigurationException.

## Environment

Nine keys are env-backed — shown here with example values:

```bash
SLUGGABLE_LANGUAGE=en
SLUGGABLE_FALLBACK_LOCALE=en
SLUGGABLE_ON_UPDATE=when_source_changes
SLUGGABLE_RETRIES=3
SLUGGABLE_HISTORY=true
SLUGGABLE_HISTORY_REDIRECT=true
SLUGGABLE_HISTORY_TABLE=slug_history
SLUGGABLE_HISTORY_PRUNE_DAYS=365
SLUGGABLE_KEY_TYPE=bigint
```

## Per-definition overrides

The defaults block only fills options a slug definition leaves unset. Anything you set on a SlugDefinition or a #\[Slug\] attribute wins for that column.

## 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/sluggable-for-laravel.md)
