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 anINSERTquery is executed.
- We check if
$post->slugis empty. If the developer didn't pass a custom slug, Laravel usesStr::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 ofboot(): When overriding lifecycle methods in modern Laravel, preferprotected static function booted(): void. If you overrideboot(), you must callparent::boot()or Laravel's internal booting logic will break.
creatingvssaving: Usecreatingif 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 thesavingevent.
- Mass Assignment: Make sure generated fields (like
uuidorslug) are present in$fillableif 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 💬
-compressed.jpg)