Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

How to Generate a UUID in Laravel 13: Str::uuid and HasUuids

๐Ÿ› ๏ธ Laravel Tutorials5 min read

How to Generate a UUID in Laravel 13: Str::uuid and HasUuids

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 7, 2026
How to Generate a UUID in Laravel 13: Str::uuid and HasUuids

To generate a UUID in Laravel 13, call Str::uuid() for a one-off value, or add the HasUuids trait to an Eloquent model and use a uuid primary key so Laravel creates the ID for you on insert. This guide targets Laravel 13 and PHP 8.3 or newer.

What is a UUID and why use it?

A UUID (universally unique identifier) is a 128-bit value such as 0190a5c2-7c8d-7e4a-9f1b-2d3c4e5f6a7b. Unlike an auto-increment number, it does not reveal how many records you have and it can be created on the client or in another service without asking the database. The cost is a larger index than an integer key.

Prerequisites

  • Laravel 13 on PHP 8.3 or newer.
  • A database such as MySQL, PostgreSQL or SQLite.

How do you generate a UUID with the Str helper?

PHP
use Illuminate\Support\Str;

$id = (string) Str::uuid();        // UUID version 4 (random)
$ordered = (string) Str::orderedUuid(); // time-ordered UUID, friendlier to indexes
$valid = Str::isUuid($id);         // true

Str::uuid() returns a UUID object, which is why we cast it to a string before storing it. Use Str::isUuid() to validate a value coming from a request or a URL.

Step 1: Create a table with a UUID primary key

Code
php artisan make:model Invoice -m
JavaScript
<?php

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('invoices', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->string('number')->unique();
            $table->decimal('total', 10, 2);
            $table->timestamps();
        });
    }

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

When another table references this one, use $table->foreignUuid('invoice_id')->constrained() so that the foreign key uses the same column type.

Step 2: How do you make the model generate its own UUID?

PHP
<?php

namespace App\Models;

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

class Invoice extends Model
{
    use HasUuids;

    protected $fillable = ['number', 'total'];
}

The HasUuids trait sets the model to a non-incrementing string key and fills the key when you create a record. In current Laravel versions, it creates time-ordered UUIDs (version 7), which keep database indexes compact. If your project must use random version 4 UUIDs, use the HasVersion4Uuids trait instead.

JavaScript
$invoice = Invoice::create(['number' => 'INV-1001', 'total' => 250]);

$invoice->id; // "0190a5c2-..." generated by Laravel

How do you use a UUID next to the integer ID?

Some teams keep a fast integer primary key and add a public UUID column for URLs and APIs. Tell the trait which columns get UUIDs with uniqueIds():

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

class Order extends Model
{
    use HasUuids;

    public function uniqueIds(): array
    {
        return ['uuid'];
    }

    public function getRouteKeyName(): string
    {
        return 'uuid';
    }
}

Add $table->uuid('uuid')->unique(); to the migration. With getRouteKeyName() set, route model binding such as /orders/{order} looks up the UUID, and you never expose your numeric IDs.

Step 3: Use it in routes

Shell
Route::get('/invoices/{invoice}', function (Invoice $invoice) {
    return $invoice;
});

Laravel resolves the model by its primary key, so the URL contains the UUID. If a visitor passes a value that is not a valid UUID to a PostgreSQL uuid column, the database can throw an error. Add a route constraint to return 404 cleanly:

CSS
Route::get('/invoices/{invoice}', ...)->whereUuid('invoice');

How do you test the UUID behaviour?

A short feature test confirms the key is generated, has the expected format, and that a bad URL returns 404.

JavaScript
use App\Models\Invoice;
use Illuminate\Support\Str;

it('creates invoices with uuid keys', function () {
    $invoice = Invoice::create(['number' => 'INV-1', 'total' => 100]);

    expect(Str::isUuid($invoice->id))->toBeTrue();

    $this->getJson("/invoices/{$invoice->id}")->assertOk();
    $this->getJson('/invoices/not-a-uuid')->assertNotFound();
});

What are the trade-offs of UUID keys?

UUIDs take 16 bytes in binary form but are often stored as 36-character strings, so indexes are larger and joins touch more memory than with an integer. Reporting queries over millions of rows feel this first. If that is a concern, keep an integer primary key for internal joins and add the public UUID column from the earlier section for URLs. Choose the approach based on your traffic and security needs, not because it is fashionable.

Common mistakes

  • Using $table->id() in the migration while the model has HasUuids. The key type must match.
  • Forgetting foreignUuid() on related tables, so the foreign key type does not match.
  • Saving a UUID object to a column without casting it to a string.
  • Using random UUIDv4 values as clustered primary keys on large MySQL tables and noticing slow inserts. Time-ordered values avoid most of this.
  • Exposing both numeric and UUID IDs in an API and forgetting which one the route expects.

How do you verify it works?

  1. Run php artisan migrate.
  2. In php artisan tinker, run Invoice::create(['number' => 'T-1', 'total' => 10])->id. You should see a UUID.
  3. Check the database row and confirm the id column holds the same value.
  4. Open /invoices/not-a-uuid and confirm you get a 404 rather than a server error.

FAQ

Is Str::uuid() the same as Str::orderedUuid()?

No. uuid() returns a random version 4 value. orderedUuid() returns a value that sorts by creation time, which is better for database indexes.

Can I generate a UUID in the migration default?

Not portably. Let the model or the application create it. Some databases support a default function, but it is not the same across MySQL, PostgreSQL and SQLite.

Do UUIDs replace authorisation checks?

No. A UUID is hard to guess, but it is not a secret. Always authorise access with policies.

Should I use ULIDs instead?

ULIDs are shorter text values that are also sortable. Laravel supports them through the HasUlids trait if you prefer that format.

References

Article Index & Topic Tags
LaravelDatabaseEloquentUuid

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.