Auto-Generating UUIDs and Slugs: How to Use Laravel Model Boot Methods

How to auto-generate UUIDs and Slugs

How to auto-generate UUIDs and Slugs


When developing web applications in Laravel, we frequently need to generate automatic values whenever a model record is created or updated. Two of the most common requirements are generating unique slugs for SEO-friendly URLs (like blog posts) and generating UUIDs (Universally Unique Identifiers) for secure, non-guessable identifiers.

Instead of manually writing slug or UUID generation logic in every controller or service before calling save(), Laravel allows us to automate this process directly inside our Eloquent models using model boot methods and lifecycle events.

In this tutorial, we will learn how Eloquent model boot methods work, how to automatically generate UUIDs and slugs, and modern alternatives available in the latest Laravel versions.

What is a Model Boot Method in Laravel?

In Eloquent, every model goes through an initialization lifecycle. When a model class is booted for the first time in a request lifecycle, Laravel executes its internal booted() (or boot()) static method.

Inside this method, we can hook into Eloquent model events such as:

  • creating & created
  • updating & updated
  • saving & saved
  • deleting & deleted

By listening to the creating or saving events inside booted(), we can set default attributes—like UUIDs and slugs—before the record is actually inserted into the database table.

Method 1: Auto-Generating Slugs Using booted()

Let's take a practical example of a Post model where every blog post needs a URL-friendly slug generated from its title.

1. Create the Migration

First, ensure your posts table has a slug column with a unique index:

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('content');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};


2. Hook into the creating Event in the Model

Now, open your Post model and define the static booted() method:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class Post extends Model
{
    protected $fillable = [
        'title',
        'slug',
        'content',
    ];

    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::creating(function (Post $post) {
            if (empty($post->slug)) {
                $post->slug = Str::slug($post->title);
            }
        });
    }
}


Let's understand what is happening here:

  • The static::creating() hook runs right before an INSERT query is executed.
  • We check if $post->slug is empty. If the developer didn't pass a custom slug, Laravel uses Str::slug() to generate one automatically from the title.

3. Handling Duplicate Slugs

In real-world applications, two posts might have the exact same title. If your database table has a unique index on the slug column, inserting a duplicate slug will throw an SQL error.

Here is how we can ensure the slug is always unique before saving:

protected static function booted(): void
{
    static::creating(function (Post $post) {
        $baseSlug = Str::slug($post->title);
        $slug = $baseSlug;
        $count = 1;

        // Check if the slug already exists in the database
        while (static::where('slug', $slug)->exists()) {
            $slug = "{$baseSlug}-{$count}";
            $count++;
        }

        $post->slug = $slug;
    });
}


Method 2: Auto-Generating UUIDs Using booted()

UUIDs (Universally Unique Identifiers) prevent users from guessing sequential IDs in URLs or APIs (e.g., changing /orders/1 to /orders/2).

1. Add UUID to Migration

Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->uuid('uuid')->unique();
    $table->foreignId('user_id')->constrained();
    $table->decimal('total', 10, 2);
    $table->timestamps();
});


2. Generate UUID in the Model

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class Order extends Model
{
    protected $fillable = [
        'uuid',
        'user_id',
        'total',
    ];

    protected static function booted(): void
    {
        static::creating(function (Order $order) {
            if (empty($order->uuid)) {
                $order->uuid = (string) Str::uuid();
            }
        });
    }
}


Whenever you create a new order, Laravel assigns a valid UUID string like d3b07384-d113-40e1-b4f0-43a9925232a5 automatically.

Method 3: Creating a Reusable Trait (Best Practice)

If multiple models across your project need auto-generated UUIDs or slugs, writing the same booted() method in every model violates the DRY (Don't Repeat Yourself) principle.

Laravel supports trait booting conventions: any trait named boot{TraitName} will be executed automatically when the model boots.

Reusable HasUuid Trait

Create a trait in app/Traits/HasUuid.php:

namespace App\Traits;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

trait HasUuid
{
    /**
     * Boot function from Laravel convention: boot{TraitName}
     */
    protected static function bootHasUuid(): void
    {
        static::creating(function (Model $model) {
            $column = $model->getUuidColumnName();

            if (empty($model->{$column})) {
                $model->{$column} = (string) Str::uuid();
            }
        });
    }

    /**
     * Get the column name for the UUID.
     */
    public function getUuidColumnName(): string
    {
        return property_exists($this, 'uuidColumn') ? $this->uuidColumn : 'uuid';
    }
}


Now, simply use this trait in any model:

namespace App\Models;

use App\Traits\HasUuid;
use Illuminate\Database\Eloquent\Model;

class Invoice extends Model
{
    use HasUuid;

    protected $fillable = ['uuid', 'amount', 'status'];
}


Modern Laravel Alternative: HasUuids & HasUlids Traits

Starting in modern Laravel versions, Laravel includes built-in traits for models that use UUIDs or ULIDs as primary keys.

If your model uses a UUID directly as the primary id, you can simply use the built-in HasUuids trait:

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;

class Client extends Model
{
    use HasUuids;

    protected $fillable = ['name', 'email'];
}


Note: Use Laravel's built-in HasUuids trait if the UUID is your model's primary key. If you are keeping an auto-incrementing id and adding a separate uuid column, use the custom booted() method or custom trait shown earlier.

Things to Remember

  • Use booted() instead of boot(): When overriding lifecycle methods in modern Laravel, prefer protected static function booted(): void. If you override boot(), you must call parent::boot() or Laravel's internal booting logic will break.
  • creating vs saving: Use creating if the slug or UUID should only be generated once when the record is first inserted. If you want the slug to re-generate every time the title is updated, use the saving event.
  • Mass Assignment: Make sure generated fields (like uuid or slug) are present in $fillable if you allow manual inputs or mass assignments.

Conclusion:

Model boot methods provide a clean, centralized way to automatically populate model attributes like UUIDs and slugs. By leveraging the booted() method or reusable custom traits, you keep your controllers thin and ensure consistency across your entire application database layer.

Thank you for reading this article 😊

For any query, do not hesitate to comment 💬



Previous Post Next Post

Contact Form