An opinionated package to create slugs for Eloquent models

Last update: May 20, 2022

Generate slugs when saving Eloquent models

Latest Version on Packagist MIT Licensed GitHub Workflow Status Total Downloads

This package provides a trait that will generate a unique slug when saving any Eloquent model.

$model = new EloquentModel();
$model->name = 'activerecord is awesome';
$model->save();

echo $model->slug; // outputs "activerecord-is-awesome"

The slugs are generated with Laravels Str::slug method, whereby spaces are converted to '-'.

Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects on our website.

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Installation

You can install the package via composer:

composer require spatie/laravel-sluggable

Usage

Your Eloquent models should use the Spatie\Sluggable\HasSlug trait and the Spatie\Sluggable\SlugOptions class.

The trait contains an abstract method getSlugOptions() that you must implement yourself.

Your models' migrations should have a field to save the generated slug to.

Here's an example of how to implement the trait:

namespace App;

use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Illuminate\Database\Eloquent\Model;

class YourEloquentModel extends Model
{
    use HasSlug;

    /**
     * Get the options for generating the slug.
     */
    public function getSlugOptions() : SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('name')
            ->saveSlugsTo('slug');
    }
}

With its migration:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateYourEloquentModelTable extends Migration
{
    public function up()
    {
        Schema::create('your_eloquent_models', function (Blueprint $table) {
            $table->increments('id');
            $table->string('slug'); // Field name same as your `saveSlugsTo`
            $table->string('name');
            $table->timestamps();
        });
    }
}

Using slugs in routes

To use the generated slug in routes, remember to use Laravel's implicit route model binding:

namespace App;

use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Illuminate\Database\Eloquent\Model;

class YourEloquentModel extends Model
{
    use HasSlug;

    /**
     * Get the options for generating the slug.
     */
    public function getSlugOptions() : SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('name')
            ->saveSlugsTo('slug');
    }

    /**
     * Get the route key for the model.
     *
     * @return string
     */
    public function getRouteKeyName()
    {
        return 'slug';
    }
}

Using multiple fields to create the slug

Want to use multiple field as the basis for a slug? No problem!

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom(['first_name', 'last_name'])
        ->saveSlugsTo('slug');
}

Customizing slug generation

You can also pass a callable to generateSlugsFrom.

By default the package will generate unique slugs by appending '-' and a number, to a slug that already exists.

You can disable this behaviour by calling allowDuplicateSlugs.

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->allowDuplicateSlugs();
}

Limiting the length of a slug

You can also put a maximum size limit on the created slug:

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->slugsShouldBeNoLongerThan(50);
}

The slug may be slightly longer than the value specified, due to the suffix which is added to make it unique.

You can also use a custom separator by calling usingSeparator

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->usingSeparator('_');
}

Setting the slug language

To set the language used by Str::slug you may call usingLanguage

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->usingLanguage('nl');
}

Overriding slugs

You can also override the generated slug just by setting it to another value than the generated slug.

$model = EloquentModel::create(['name' => 'my name']); //slug is now "my-name";
$model->slug = 'my-custom-url';
$model->save(); //slug is now "my-custom-url";

Prevent slugs from being generated on creation

If you don't want to create the slug when the model is initially created you can set use the doNotGenerateSlugsOnCreate() function.

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->doNotGenerateSlugsOnCreate();
}

Prevent slug updates

Similarly, if you want to prevent the slug from being updated on model updates, call doNotGenerateSlugsOnUpdate().

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->doNotGenerateSlugsOnUpdate();
}

This can be helpful for creating permalinks that don't change until you explicitly want it to.

$model = EloquentModel::create(['name' => 'my name']); //slug is now "my-name";
$model->save();

$model->name = 'changed name';
$model->save(); //slug stays "my-name"

Regenerating slugs

If you want to explicitly update the slug on the model you can call generateSlug() on your model at any time to make the slug according to your other options. Don't forget to save() the model to persist the update to your database.

Preventing overwrites

You can prevent slugs from being overwritten.

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->preventOverwrite();
}

Using scopes

If you have a global scope that should be taken into account, you can define this as well with extraScope. For example if you have a pages table containing pages of multiple websites and every website has it's own unique slugs.

public function getSlugOptions() : SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('name')
        ->saveSlugsTo('slug')
        ->extraScope(fn ($builder) => $builder->where('scope_id', $this->scope_id));
}

Integration with laravel-translatable

You can use this package along with laravel-translatable to generate a slug for each locale. Instead of using the HasSlug trait, you must use the HasTranslatableSlug trait, and add the name of the slug field to the $translatable array. For slugs that are generated from a single field or multiple fields, you don't have to change anything else.

namespace App;

use Spatie\Sluggable\HasTranslatableSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;
use Illuminate\Database\Eloquent\Model;

class YourEloquentModel extends Model
{
    use HasTranslations, HasTranslatableSlug;

    public $translatable = ['name', 'slug'];

    /**
     * Get the options for generating the slug.
     */
    public function getSlugOptions() : SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('name')
            ->saveSlugsTo('slug');
    }
}

For slugs that are generated from a callable, you need to instantiate the SlugOptions with the createWithLocales method. The callable now takes two arguments instead of one. Both the $model and the $locale are available to generate a slug from.

namespace App;

use Spatie\Sluggable\HasTranslatableSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;
use Illuminate\Database\Eloquent\Model;

class YourEloquentModel extends Model
{
    use HasTranslations, HasTranslatableSlug;

    public $translatable = ['name', 'slug'];

    /**
     * Get the options for generating the slug.
     */
    public function getSlugOptions() : SlugOptions
    {
        return SlugOptions::createWithLocales(['en', 'nl'])
            ->generateSlugsFrom(function($model, $locale) {
                return "{$locale} {$model->id}";
            })
            ->saveSlugsTo('slug');
    }
}

Changelog

Please see CHANGELOG for more information what has changed recently.

Testing

composer test

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

GitHub

https://github.com/spatie/laravel-sluggable
Comments
  • 1. Generating Slug on Replicated Model

    Hi,

    I just defined SlugOptions in my model so:

    public function getSlugOptions() : SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('article_title')
            ->saveSlugsTo('slug')
            ->usingSeparator('-')
            ->doNotGenerateSlugsOnUpdate();
    }
    

    and I'm trying to generate slug on model created via replicate method.

    Because of doNotGenerateSlugsOnUpdate(), this is not working. Is there any possible workaround for this?

    Reviewed by mahony0 at 2021-05-06 00:18
  • 2. Issue when Model uses UUIDs

    Hi there,

    first of all: Thank you for your work! I really appreciate your packages and the features you provide to the community.

    I implemented the sluggable package into one of my uuid-enabled models. Unfortunately there seems to be a problem at the time the package looks for uniqueness in the database.

    Here are the code which I'm using at the Model:

    class Item extends Model
    {
        use Uuid, SoftDeletes, HasSlug;
    
        /**
         * The attributes that are mass assignable.
         *
         * @var array
         */
        protected $fillable = [
            'name',
        ];
    
        /**
         * Indicates if the IDs are auto-incrementing.
         *
         * @var bool
         */
        public $incrementing = false;
    
        /**
         * The attributes that should be hidden for arrays.
         *
         * @var array
         */
        protected $hidden = [];
    
        /**
         * The attributes that should be mutated to dates.
         *
         * @var array
         */
        protected $dates = [
            'created_at',
            'updated_at',
            'deleted_at'
        ];
    
        /**
         * Get the options for generating the slug.
         */
        public function getSlugOptions() : SlugOptions
        {
            return SlugOptions::create()
                ->generateSlugsFrom('name')
                ->saveSlugsTo('slug');
        }
    }
    

    On inserting a new record I'm receiving the following exception: SQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for type uuid: "0" (SQL: select * from "items" where "slug" = test and "id" != 0 limit 1)

    Do you have any idea how to fix this the right way?

    Thanks in advance for your help and advice.

    Kind regards Martin

    Reviewed by mkriegeskorte at 2018-02-10 18:47
  • 3. Implicit route model binding won't work with laravel-translatable integration

    Whenever I visit the resourceful controller URL with getRouteKeyName() set to slug on the model, I get 404. I don't see tests with controller, maybe that's why this issue is not covered. Project setup: Laravel 8, laravel-sluggable and laravel-translatable packages installed.

    Model:

    class Restaurant extends Model
    {
        use HasFactory, HasTranslations, HasTranslatableSlug;
    
        /**
         * The attributes that aren't mass assignable.
         *
         * @var array
         */
        protected $guarded = [];
    
        /**
         * The attributes that are translatable.
         *
         * @var array
         */
        protected $translatable = ['name', 'slug'];
    
        /**
         * Get the options for generating the slug.
         *
         * @return \Spatie\Sluggable\SlugOptions
         */
        public function getSlugOptions(): SlugOptions
        {
            return SlugOptions::create()
                ->generateSlugsFrom('name')
                ->saveSlugsTo('slug');
        }
    ...
    

    api.php:

    Route::apiResource('restaurants', RestaurantController::class)
        ->scoped(['restaurant' => 'slug']); // Alternative to getRouteKeyName(), which doesn't work either
    

    Saved model in the database: image

    dummy workflow: Request: GET /api/restaurants/aaaa-bbbb Response: 404 Not found

    Request: GET /api/restaurants/cccc-dddd Response: 404 Not found

    Request: GET /api/restaurants/1 Response: 404 Not found

    config/app.php

    ...
    'locale' => 'en',
    'fallback_locale' => 'en',
    ...
    
    Reviewed by andriihorpenko at 2020-09-17 15:21
  • 4. Slug Arabic problem

    when creating a slug from a name . the slug is in English letters example :

    name = مقالة جديدة slug = mk-l-gdyd

    it should be: مقالة-جديدة

    Any idea how to solve this issue?

    Reviewed by AO-IO at 2019-10-20 23:47
  • 5. Slugs are not created for UTF-8 Characters.

    I have a website which supports title in HINDI. I am using the plugin but instead of slug m getting an empty string but as the unique slugs are created it returns as only "-number" e.g "-1", "-2".

    It would be a great help if you can look into this matter.

    I am attaching the screenshot for more clarification.

    Screenshot (31)

    Reviewed by msamgan at 2019-05-28 05:40
  • 6. Added $slugLanguage option

    As discussed in https://github.com/spatie/laravel-sluggable/issues/34:

    • Added $slugLanguage to SlugOptions
    • Passed $slugLanguage to generateNonUniqueSlug() in HasSlug
    • Added test to check if language gets set
    • Updated README with an example on how to use usingLanguage()

    First open source PR ever, be gentle! ;-)

    Reviewed by joshuadegier at 2017-09-12 22:41
  • 7. Fix Eloquent model checking

    Hi,

    Following a bug on my app, I propose a modification of the check of model existence in otherRecordExistsWithSlug.

    The $this->exists() method performs a global check of table (excluding soft delete), and doesn't check the real existence of the model. In addition it can lead to unexpected behavior such as recreating a new slug if the whole table is soft-deleted because $this->exists() will return false.

    Example request with this method (thanks Ray 💯) :

    select exists(select * from `accounts` where `accounts`.`deleted_at` is null) as `exists`
    

    All entries have a deleted_at column filled in, so when I restore an entry the slug changes from 'example-slug' to 'example-slug-1' because the eloquent model is not excluded from the request.

    I propose to replace $this->exists() by $this->exists which directly confirms the existence of the model.

    I don't know the impact of this change on the previous PR regarding postgresql.

    I'm sorry in advance for my approximate English where if I do something wrong it's only my second PR on an open-source project.

    Regards...

    Reviewed by aurelien31 at 2021-01-28 18:22
  • 8. Add support for Laravel 6.0

    Referenced in https://github.com/spatie/laravel-sluggable/issues/113

    I am unsure if the version constraint for PHP 7.2 is required. Laravel 6.0 requires 7.2+ but, I think maybe leaving the PHP version at >=7.1.3 is fine and allowing the project itself to define the PHP version. If you have feedback that'd be great!

    Thanks!

    Reviewed by belzaaron at 2019-08-28 14:13
  • 9. Problem with uniqueness and global scopes

    Hi, I`m working with a db that has defined the slug field like unique, and use softdeletes for deleted info, the problem is that the slug check for unique value in this package don't check softdelete.

    Can you help me with this. Thanks and sorry for the english

    Reviewed by Artessan at 2016-06-21 14:03
  • 10. Ability to enable/disable slug generation on demand

    As I have inspected there is no option to to enable/disable slug generation on demand.

    Suppose I have a model which has

    public function getSlugOptions() : SlugOptions
        {
            return SlugOptions::create()
                ->generateSlugsFrom('title')
                ->saveSlugsTo('slug')
                ->doNotGenerateSlugsOnUpdate();
        }
    

    but sometimes I do not want to create the slug so it does not generate the query for checking duplication or generation and we are manually setting it. So thus cases we don't want the slug generation to trigger. Is there any possibility to do it maybe I may have missed. Or if you think it can solved using simple hack I would be glad to make a PR out of it.

    Reviewed by msonowal at 2020-05-04 09:12
  • 11. Allow route binding of slug in addition to routeKeyName

    This pull request adds a method to allow the generated slug to be used along side the default (or overridden) getRouteKeyName() method. Simply adding another method on the SlugOptions configuration chain can opt-in to this feature. With the addition of the allowRouteBinding() in the config you may now instantly use the slug without worrying about converting the whole system to slugs (and it just feels better to me :P)

    I'm struggling to find how I can reliably test this so, please if you have any guidance I would love to hear it!

    I know that I usually add this feature as another trait that extends the HasSlug from this package but, I would like to put this out to maybe anyone who wants to do this with just the package.

    Please let me know how you feel about this and if you have any constructive feedback please, I'm all ears!

    Reviewed by belzaaron at 2019-08-29 18:17
  • 12. Support Arabic letters with tests

    Test added for #222

    Thank you @freekmurze for your reply to #222 asking me to add tests for this feature, I added tests in this BR Because I deleted the previous fork.

    Because Laravels Str::slug method does not support Arabic letters, So You can override the generated slug from Str::slug by calling arabicable() to generate slugs in Arabic !

    Thank you!

    Reviewed by Salah3id at 2022-03-13 21:25
Use auto generated UUID slugs to identify and retrieve your Eloquent models.

Laravel Eloquent UUID slug Summary About Features Requirements Installation Examples Compatibility table Alternatives Tests About By default, when get

Apr 4, 2022
Simple slugs for your Laravel models.

Simple slugs for your Laravel models. This packages provides an opinionated, attribute-driven trait for automatically generating slugs when creating L

Apr 29, 2022
Create presenters for Eloquent Models
Create presenters for Eloquent Models

Laravel Presentable This package allows the information to be presented in a different way by means of methods that can be defined in the model's pres

Mar 2, 2022
A simple to use opinionated ERP package to work with Laravel

Laravel ERP A simple to use opinionated ERP package to work with Laravel Installation You can install the package via composer: composer require justs

May 6, 2022
An opinionated support package for Laravel, that provides flexible and reusable helper methods and traits for commonly used functionality.

Support An opinionated support package for Laravel, that provides flexible and reusable helper methods and traits for commonly used functionality. Ins

Apr 14, 2021
An opinionated feature flags package for Laravel

This package provides an opinionated API for implementing feature flags in your Laravel applications. It supports application-wide features as well as model specific feature flags.

May 23, 2022
This package gives Eloquent models the ability to manage their friendships.

Laravel 5 Friendships This package gives Eloquent models the ability to manage their friendships. You can easily design a Facebook like Friend System.

May 23, 2022
A small package for adding UUIDs to Eloquent models.

A small package for adding UUIDs to Eloquent models. Installation You can install the package via composer: composer require ryangjchandler/laravel-uu

Mar 17, 2022
Laravel package to search through multiple Eloquent models.

Laravel package to search through multiple Eloquent models. Supports sorting, pagination, scoped queries, eager load relationships and searching through single or multiple columns.

May 18, 2022
A package to generate YouTube-like IDs for Eloquent models

Laravel Hashids This package provides a trait that will generate hashids when saving any Eloquent model. Hashids Hashids is a small package to generat

May 12, 2022
Laravel Segment is an opinionated, approach to integrating Segment into your Laravel application.

Laravel Segment Laravel Segment is an opinionated, approach to integrating Segment into your Laravel application. Installation You can install the pac

May 16, 2022
An opinionated blade template formatter for Laravel that respects readability
An opinionated blade template formatter for Laravel that respects readability

blade-formatter An opinionated blade template formatter for Laravel that respects readability Online Demo Features Automatically Indents markup inside

May 24, 2022
An Eloquent Way To Filter Laravel Models And Their Relationships

Eloquent Filter An Eloquent way to filter Eloquent Models and their relationships Introduction Lets say we want to return a list of users filtered by

May 27, 2022
Sortable behaviour for Eloquent models
Sortable behaviour for Eloquent models

Sortable behaviour for Eloquent models This package provides a trait that adds sortable behaviour to an Eloquent model. The value of the order column

May 23, 2022
Automatically validating Eloquent models for Laravel

Validating, a validation trait for Laravel Validating is a trait for Laravel Eloquent models which ensures that models meet their validation criteria

May 26, 2022
Laravel Ban simplify blocking and banning Eloquent models.
Laravel Ban simplify blocking and banning Eloquent models.

Laravel Ban Introduction Laravel Ban simplify management of Eloquent model's ban. Make any model bannable in a minutes! Use case is not limited to Use

May 25, 2022
May 23, 2022
Record created by, updated by and deleted by on Eloquent models automatically.

quarks/laravel-auditors Record created by, updated by and deleted by (if SoftDeletes added) on Eloquent models automatically. Installation composer re

Jan 8, 2022
Observe (and react to) attribute changes made on Eloquent models.
Observe (and react to) attribute changes made on Eloquent models.

Laravel Attribute Observer Requirements PHP: 7.4+ Laravel: 7+ Installation You can install the package via composer: composer require alexstewartja/la

Mar 5, 2022