An opinionated package to create slugs for Eloquent models

Overview

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.

Comments
  • Added $slugLanguage option

    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! ;-)

    opened by joshuadegier 7
  • add forbidden slug option

    add forbidden slug option

    This PR allows the ability to specify forbidden slugs that should NOT be generated. My use case was to avoid route collision. I have models with soft deletes and setup routes to handle various tasks like:

    model/{model}
    model/trashed // index of all soft deleted models
    

    What if the user creates a model that generates one of these slugs? They won't be able access their model or perform these tasks. There are probably more use cases than this one.

    class Page extends Model
    {
            HasSlug;
    
            public function getSlugOptions(): SlugOptions
            {
                return parent::getSlugOptions()
                    ->forbiddenSlugs(['trashed', 'annihilate', 'restore']);
            }
    }
    
    opened by hotsaucejake 6
  • Fix Eloquent model checking

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

    opened by aurelien31 6
  • Add support for Laravel 6.0

    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!

    opened by belzaaron 6
  • Allow route binding of slug in addition to routeKeyName

    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!

    opened by belzaaron 5
  • Add option to check soft deleted models for uniqueness

    Add option to check soft deleted models for uniqueness

    This is an attempt to address #12. I apologize if this isn't what the author intended but I think I understood correctly.

    I'm not sure about the naming and I'm open to feedback.

    I just realized for forgot to update the readme. Let me know if you intend to pull this in and I'll update it.

    opened by marcusmoore 5
  • Add options to manage slug processing events

    Add options to manage slug processing events

    The onCreate() and onUpdate() slug options are used to permit, or prevent slug creation/update on model save. By default both are set to true which means there is no change in the existing functionality.

    Setting onUpdate(false) will prevent a new slug from being calculated and overwrite the existing slug. This is very useful for creating permanent slugs which do not automatically change once their created even if the field the slug was generated from changes (think permalinks created based on blog titles).

    Setting onCreate(false) will prevent a slug from being calculated and saved to the model on creation. This may be useful to prevent creating slugs for some models until some time after the creation (think again of permalinks not created until a blog article is actually published).

    opened by Indemnity83 4
  • Shareable Slugable

    Shareable Slugable

    What

    Adds possibility to share URLs with localized slugs between different preferred user locales.

    Why

    When sharing a link with a local slug to a person with a different navigator language, 404 is returned even though the model exists with a different locale. The proposed changes introduce a new database table which stores all translated slugs. This approach was chosen as querying for JSON values is highly inefficient with large datasets. With thus change, the developer can now decide, wherever the user should see the model with the intended locale or the user's preferred locale.

    Changes

    This PR introduces no BC, but has some changes in the usage of the new trait.

    • Some methods in HasSlug and HasTranslateableSlug have been split up to allow them to be partially overwritten.
    • Shareable slugs are now stored in a separate slugs table.
    • A service provider was added for migration.
    • The slugs are automatically changed when ->setSlug is called.
    • Non-unique slugs are not supported
    • The access with $model->slug is no longer available, as this would require getAttributeValue and setAttributeto be overwritten. This would conflict with the Translateable trait. Any ideas on how to solve this are appreciated!
    • The slugs are saved at the created and updated event, as the model needs to exist in order to save the slug.
    • In case a slug is defined before model creation, the slug is stored locally until the model is created.
    opened by marvin-wtt 3
  • Add extra scope callback option

    Add extra scope callback option

    When a table has an extra scope that needs to be accounted for, you can add this scope to the SlugOptions. For instance, when you have a table pages which contains multiple pages for multiple sites. Every site could have it's own unique slugs. Therefor you want to be able to add an extra scope.

    You can now do this by adding to the Page model:

     public function getSlugOptions() : SlugOptions
        {
            $options = SlugOptions::create()
                ->generateSlugsFrom('title')
                ->saveSlugsTo('slug')
                ->extraScope(fn ($builder) => $builder->where('site_id', $this->site_id));
    
            return $options;
        }
    
    opened by sietzekeuning 3
  • Laravel 8.x Compatibility

    Laravel 8.x Compatibility

    This is an automated pull request from Shift to update your package code and dependencies to be compatible with Laravel 8.x.

    Before merging, you need to:

    • Checkout the l8-compatibility branch
    • Review all comments for additional changes
    • Thoroughly test your package

    If you do find an issue, please report it by commenting on this PR to help improve future automation.

    opened by laravel-shift 3
  • Add support for Laravel 8

    Add support for Laravel 8

    Adds support for the soon-to-be-released Laravel 8.

    ~~This pull request depends on https://github.com/spatie/laravel-translatable/pull/226~~ PR already merged into master.

    ~~If a new release is tagged for spatie/laravel-translatable, then it needs to be also updated here.~~ Update to package was tagged with a minor version, so no other changes are needed here.

    ~~Apart from the "unmet requirements" for tests with Laravel 8 support, the tests are failing because of issue #163.~~ Fix already merged into master, so tests should be green now.

    opened by axelitus 3
Releases(3.4.1)
  • 3.4.1(Dec 7, 2022)

    What's Changed

    • Update README.md by @furiouskj in https://github.com/spatie/laravel-sluggable/pull/240
    • composer allow-plugins config by @hotsaucejake in https://github.com/spatie/laravel-sluggable/pull/241
    • Normalize composer.json by @patinthehat in https://github.com/spatie/laravel-sluggable/pull/247
    • Add Dependabot Automation by @patinthehat in https://github.com/spatie/laravel-sluggable/pull/246
    • Add PHP 8.2 Support by @patinthehat in https://github.com/spatie/laravel-sluggable/pull/245
    • Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/spatie/laravel-sluggable/pull/248
    • Allow set slug suffix starting number by @Vediovis in https://github.com/spatie/laravel-sluggable/pull/249

    New Contributors

    • @furiouskj made their first contribution in https://github.com/spatie/laravel-sluggable/pull/240
    • @hotsaucejake made their first contribution in https://github.com/spatie/laravel-sluggable/pull/241
    • @dependabot made their first contribution in https://github.com/spatie/laravel-sluggable/pull/248
    • @Vediovis made their first contribution in https://github.com/spatie/laravel-sluggable/pull/249

    Full Changelog: https://github.com/spatie/laravel-sluggable/compare/3.4.0...3.4.1

    Source code(tar.gz)
    Source code(zip)
  • 3.4.0(Mar 28, 2022)

    What's Changed

    • Converts Test cases to Pest tests by @marvin-wtt in https://github.com/spatie/laravel-sluggable/pull/223
    • Add ability to skip the slug generation by a condition by @masterix21 in https://github.com/spatie/laravel-sluggable/pull/227

    New Contributors

    • @masterix21 made their first contribution in https://github.com/spatie/laravel-sluggable/pull/227

    Full Changelog: https://github.com/spatie/laravel-sluggable/compare/3.3.1...3.4.0

    Source code(tar.gz)
    Source code(zip)
  • 3.3.1(Mar 9, 2022)

    What's Changed

    • Add support for spatie/laravel-translatable:^6.0 by @mziraki in https://github.com/spatie/laravel-sluggable/pull/224

    New Contributors

    • @mziraki made their first contribution in https://github.com/spatie/laravel-sluggable/pull/224

    Full Changelog: https://github.com/spatie/laravel-sluggable/compare/3.3.0...3.3.1

    Source code(tar.gz)
    Source code(zip)
  • 3.3.0(Jan 13, 2022)

  • 3.2.0(Dec 15, 2021)

    What's Changed

    • Adds support for implicit route model binding with translated slugs by @marvin-wtt in https://github.com/spatie/laravel-sluggable/pull/213

    New Contributors

    • @marvin-wtt made their first contribution in https://github.com/spatie/laravel-sluggable/pull/213

    Full Changelog: https://github.com/spatie/laravel-sluggable/compare/3.1.1...3.2.0

    Source code(tar.gz)
    Source code(zip)
  • 3.1.1(Dec 13, 2021)

    What's Changed

    • Migrate to PHP-CS-Fixer 3.x by @shuvroroy in https://github.com/spatie/laravel-sluggable/pull/203
    • Adds test case for replicate method by @eduarguz in https://github.com/spatie/laravel-sluggable/pull/212
    • Fix Deprecation: currentSlug is null by @phh in https://github.com/spatie/laravel-sluggable/pull/218

    New Contributors

    • @shuvroroy made their first contribution in https://github.com/spatie/laravel-sluggable/pull/203
    • @eduarguz made their first contribution in https://github.com/spatie/laravel-sluggable/pull/212
    • @phh made their first contribution in https://github.com/spatie/laravel-sluggable/pull/218

    Full Changelog: https://github.com/spatie/laravel-sluggable/compare/3.1.0...3.1.1

    Source code(tar.gz)
    Source code(zip)
  • 3.1.0(Jun 4, 2021)

  • 3.0.2(May 7, 2021)

  • 3.0.1(Apr 22, 2021)

  • 3.0.0(Mar 26, 2021)

    • require PHP 8+
    • drop support for PHP 7.x
    • convert syntax to PHP 8
    • move Exceptions to Exceptions folder to match structure of other packages
    Source code(tar.gz)
    Source code(zip)
  • 2.6.2(Mar 20, 2021)

  • 2.6.1(Jan 31, 2021)

  • 2.6.0(Oct 28, 2020)

  • 2.5.2(Oct 1, 2020)

  • 2.5.1(Sep 9, 2020)

  • 2.5.0(Jun 15, 2020)

  • 2.4.2(Apr 20, 2020)

  • 2.4.1(Apr 9, 2020)

  • 2.2.2(Apr 6, 2020)

  • 2.4.0(Mar 3, 2020)

  • 2.3.0(Dec 6, 2019)

  • 2.2.1(Sep 16, 2019)

  • 2.2.0(Sep 4, 2019)

  • 2.1.8(Jun 8, 2019)

  • 2.1.7(Feb 27, 2019)

  • 2.1.5(Jan 10, 2019)

  • 2.1.4(Aug 28, 2018)

  • 1.5.2(May 8, 2018)

  • 2.1.3(Feb 15, 2018)

Owner
Spatie
We create open source, digital products and courses for the developer community
Spatie
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

Khalyomede 25 Dec 14, 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

Ryan Chandler 20 Aug 15, 2022
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

The Hive Team 67 Dec 7, 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.

Alex Kyriakidis 690 Nov 27, 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

Ryan Chandler 40 Jun 5, 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.

Protone Media 845 Jan 1, 2023
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

Λгi 25 Aug 31, 2022
Package with small support traits and classes for the Laravel Eloquent models

Contains a set of traits for the eloquent model. In future can contain more set of classes/traits for the eloquent database.

Martin Kluska 3 Feb 10, 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

Steve McDougall 16 Nov 30, 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

Ian Olson 3 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.

Ryan Chandler 157 Nov 24, 2022
Laravel package to create autonumber for Eloquent model

Laravel AutoNumber Laravel package to create autonumber for Eloquent model Installation You can install the package via composer: composer require gid

null 2 Jul 10, 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

Eric Tucker 1.5k Jan 7, 2023
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

Spatie 1.2k Dec 22, 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

Dwight Watson 955 Dec 25, 2022
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

cybercog 879 Dec 30, 2022
cybercog 996 Dec 28, 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

Quarks 3 Jun 13, 2022
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

Alex Stewart 55 Jan 4, 2023