---
title: "Slug history & redirects — Sluggable for Laravel | Roundly"
description: "Keep retired slugs and 301-redirect old URLs to the current one — without revealing drafts, trashed rows or other tenants’ records."
url: https://roundly-consulting.com/open-source/docs/sluggable-for-laravel/history
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)

# Slug history & redirects

Opt a definition into history (or set history.enabled globally), then publish and run the history migration:

```php
SlugDefinition::for('slug')->from('name')->regenerateOnUpdate()->keepHistory()->routeKey();
```

```bash
php artisan vendor:publish --tag="sluggable-migrations"
php artisan migrate
```

## How redirects work

- Every persisted change retires the previous value into slug\_history. A model taking back one of its own old slugs removes that entry.
- A GET or HEAD request with a retired slug renders a 301 (history.redirect\_status) to the same route with the current slug, keeping the query string. Other methods get a 404.
- The target is resolved through the route’s own query — global scopes, the parent of a scoped binding, trashed handling — so drafts, other tenants’ rows and trashed rows are never revealed.
- Only the matched route segment comes from input, so there is no open redirect. SlugMovedException implements ShouldntReport; it never reaches your error log.
- redirectFromHistory(false) keeps the history without redirecting; avoidHistoricalSlugs() keeps other models from taking a retired slug — for a scoped definition (uniqueWithin() / uniqueWhere()) only slugs retired in the same scope, since redirects never cross scopes.

## Lookups

Slugs::model() looks a retired slug up through the model’s default query (global scopes applied) or the query you pass as within:

```php
use RoundlyConsulting\Sluggable\Facades\Slugs;

Slugs::model(Product::class)->findInHistory('old-name', column: 'slug', locale: 'sk');   // ?Model

// Resolve only within a query you trust — a retired slug never reveals a row it would not return
Slugs::model(Product::class)->findInHistory('old-name', within: Product::query()->where('shop_id', $shop->id));
```

## Cleanup and pruning

History rows are removed when the model is really deleted (no SoftDeletes, or a force delete); a soft delete keeps them. Mass deletes that fire no model events leave inert orphan rows — pruning ages them out. Set history.prune\_after\_days and schedule Laravel’s model:prune for the history model (or your configured history.model):

```php
// routes/console.php, with sluggable.history.prune_after_days set
Schedule::command('model:prune', ['--model' => [\RoundlyConsulting\Sluggable\Models\SlugHistory::class]]);
```

## Constraints

- The history morph id column follows sluggable.key\_type (bigint, uuid or ulid); a model whose key type does not fit is rejected at definition time (historyKeyTypeMismatch). One key type per host.
- History stores slugs of up to 255 characters; a history-keeping definition with a larger maxLength is rejected at definition time.
- The history model is swappable through history.model — your class must extend SlugHistory.
- If the history table is missing, the local and testing environments throw historyTableMissing; elsewhere a warning is logged and recording is skipped — never a 500 after the row was saved.

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