Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Sluggable Laravel Package

spatie/laravel-sluggable

Generate unique slugs for Eloquent models on create/update. Supports collision suffixes, translatable slugs, and customizable slug options. Includes self-healing URLs that keep old links working via slug+ID route keys with 308 redirects to the canonical URL.

View on GitHub
Deep Wiki
Context7

title: Overriding the underlying actions weight: 3

The package delegates low-level work to three action classes that can each be swapped for your own.

  • generate_slug generates the slug on create and update.
    Default: Spatie\Sluggable\Actions\GenerateSlugAction.
  • build_self_healing_route_key composes the {slug}{separator}{id} route key.
    Default: Spatie\Sluggable\Actions\BuildSelfHealingRouteKeyAction.
  • extract_identifier_from_self_healing_route_key splits an incoming route value back into slug and identifier.
    Default: Spatie\Sluggable\Actions\ExtractIdentifierFromSelfHealingRouteKeyAction.

Publishing the config

Publish the configuration file before pointing any of the action keys at a custom class.

php artisan vendor:publish --tag=sluggable-config

Edit the resulting config/sluggable.php and point the relevant key at your own class. Replacement classes must extend the default action so the package can type-check the override.

// config/sluggable.php
return [
    'actions' => [
        'generate_slug' => App\Sluggable\MyGenerateSlugAction::class,
        'build_self_healing_route_key' => Spatie\Sluggable\Actions\BuildSelfHealingRouteKeyAction::class,
        'extract_identifier_from_self_healing_route_key' => Spatie\Sluggable\Actions\ExtractIdentifierFromSelfHealingRouteKeyAction::class,
    ],
];

Example: uppercase slugs in the route key

Extend the default action and override its single execute() method to wrap the call.

namespace App\Sluggable;

use Spatie\Sluggable\Actions\BuildSelfHealingRouteKeyAction;

class UppercaseRouteKeyAction extends BuildSelfHealingRouteKeyAction
{
    public function execute(string $slug, int|string $identifier, string $separator): string
    {
        return parent::execute(strtoupper($slug), $identifier, $separator);
    }
}

Then point the config key at the new class.

// config/sluggable.php
'build_self_healing_route_key' => App\Sluggable\UppercaseRouteKeyAction::class,

Example: putting the identifier first

The default route key looks like hello-world-5. To flip it to 5-hello-world instead, override both self-healing actions: one to build the new format, one to parse it back into a slug and an identifier.

The build action moves the identifier in front of the slug.

namespace App\Sluggable;

use Spatie\Sluggable\Actions\BuildSelfHealingRouteKeyAction;

class IdFirstBuildAction extends BuildSelfHealingRouteKeyAction
{
    public function execute(string $slug, int|string $identifier, string $separator): string
    {
        if ($slug === '') {
            return (string) $identifier;
        }

        return "{$identifier}{$separator}{$slug}";
    }
}

The extractor reads the identifier from the front of the value instead of the end. The default uses strrpos (last separator) because slugs can contain the separator. The id-first version uses strpos (first separator), which is safe as long as the identifier never contains the separator. Numeric ids and ULIDs do not.

namespace App\Sluggable;

use Spatie\Sluggable\Actions\ExtractIdentifierFromSelfHealingRouteKeyAction;

class IdFirstExtractAction extends ExtractIdentifierFromSelfHealingRouteKeyAction
{
    public function execute(string $value, string $separator): array
    {
        $position = strpos($value, $separator);

        if ($position === false) {
            return ['slug' => $value, 'identifier' => null];
        }

        $identifier = substr($value, 0, $position);

        if ($identifier === '' || ! ctype_digit($identifier)) {
            return ['slug' => $value, 'identifier' => null];
        }

        return [
            'slug' => substr($value, $position + strlen($separator)),
            'identifier' => $identifier,
        ];
    }
}

The ctype_digit check rejects values that have no numeric prefix. Drop it (or replace it with a regex that matches your key format) when models use ULID, UUID, or other non-numeric primary keys.

Wire both classes into the config.

// config/sluggable.php
'build_self_healing_route_key' => App\Sluggable\IdFirstBuildAction::class,
'extract_identifier_from_self_healing_route_key' => App\Sluggable\IdFirstExtractAction::class,

A Post with id 5 and slug hello-world now exposes 5-hello-world as its route key, and a stale URL like /posts/5-old-title still resolves the post and 308-redirects to /posts/5-hello-world.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle