Overriding the underlying actions | laravel-sluggable | Spatie

 SPATIE

  Laravel Sluggable
====================

spatie.be/open-source

  [Docs](https://spatie.be/docs)  [Laravel-sluggable](https://spatie.be/docs/laravel-sluggable/v4)  Advanced-usage  Overriding the underlying actions

 Version   v4

 Other versions for crawler [v4](https://spatie.be/docs/laravel-sluggable/v4)

  Overriding the underlying actions
- [ Introduction ](https://spatie.be/docs/laravel-sluggable/v4/introduction)
- [ Requirements ](https://spatie.be/docs/laravel-sluggable/v4/requirements)
- [ Installation &amp; setup ](https://spatie.be/docs/laravel-sluggable/v4/installation-setup)
- [ Translatable slugs ](https://spatie.be/docs/laravel-sluggable/v4/translatable-slugs)
- [ Laravel Boost skill ](https://spatie.be/docs/laravel-sluggable/v4/laravel-boost-skill)
- [ Changelog ](https://spatie.be/docs/laravel-sluggable/v4/changelog)
- [ Questions and issues ](https://spatie.be/docs/laravel-sluggable/v4/questions-issues)
- [ Support us ](https://spatie.be/docs/laravel-sluggable/v4/support-us)
- [ Upgrading ](https://spatie.be/docs/laravel-sluggable/v4/upgrading)

Basic usage
-----------

- [ Generating your first slug ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/getting-started)
- [ Using the Sluggable attribute ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/using-the-attribute)
- [ Using the HasSlug trait ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/using-the-has-slug-trait)
- [ Finding models by slug ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/finding-models-by-slug)
- [ Self-healing URLs ](https://spatie.be/docs/laravel-sluggable/v4/basic-usage/self-healing-urls)

Advanced usage
--------------

- [ Combining multiple source columns ](https://spatie.be/docs/laravel-sluggable/v4/advanced-usage/source-fields)
- [ Tuning the uniqueness suffix ](https://spatie.be/docs/laravel-sluggable/v4/advanced-usage/uniqueness)
- [ Overriding the underlying actions ](https://spatie.be/docs/laravel-sluggable/v4/advanced-usage/overriding-actions)

 Overriding the underlying actions
=================================

###  On this page

1. [ Publishing the config ](#content-publishing-the-config)
2. [ Example: uppercase slugs in the route key ](#content-example-uppercase-slugs-in-the-route-key)
3. [ Example: putting the identifier first ](#content-example-putting-the-identifier-first)

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 `{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`.

 A good
match?
-------------

### What we do best

- All things Laravel
- Custom frontend components
- Building APIs
- AI-powered features
- Simplifying things
- Clean solutions
- Integrating services

### Not our cup of tea

- WordPress themes
- Cutting corners
- Free mockups to win a job
- "Just execute the briefing"

 In short: we'd like to be a **substantial part** of your project.

 [ Get in touch via email ](mailto:info@spatie.be?subject=A%20good%20match%21&body=Tell%20us%20as%20much%20as%20you%20can%20about%0A-%20your%20online%20project%0A-%20your%20planning%0A-%20your%20budget%0A-%20%E2%80%A6%0A%0AAnything%20that%20helps%20us%20to%20start%20straightforward%21)
