Laravel UUID: Eloquent Models, Str::uuid(), and Route Binding
- Laravel 9+ ships with the HasUuids trait — add it to your model and UUIDs are automatic.
- The migration uses $table->uuid('id')->primary() instead of $table->id().
- Route model binding works out of the box — no changes to routes or controllers.
- For older Laravel versions, override the boot() method and use Str::uuid() manually.
Table of Contents
Laravel 9 introduced the HasUuids trait, making UUID primary keys a one-line change. Add the trait to your Eloquent model, update your migration to use a UUID column, and everything else — route model binding, Eloquent queries, API resources — works without modification. This guide covers modern Laravel (9+) and the manual approach for older versions.
Laravel 9+: The HasUuids Trait
The cleanest approach in Laravel 9 and above:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
class Order extends Model
{
use HasUuids;
protected $fillable = ['customer_email', 'total'];
}
The HasUuids trait:
- Automatically generates a UUID v4 before each
INSERT. - Disables auto-increment (
$incrementing = false) and sets the key type tostring. - Works with
uniqueIds()if you want UUIDs on non-primary columns too.
Migration: UUID Column as Primary Key
Replace $table->id() with a UUID column:
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('orders', function (Blueprint $table) {
$table->uuid('id')->primary(); // not $table->id()
$table->string('customer_email');
$table->decimal('total', 10, 2);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('orders');
}
};
In PostgreSQL, $table->uuid() creates a native UUID column. In MySQL, it creates CHAR(36). Run php artisan migrate as normal.
Laravel 8 and Below: Manual UUID in boot()
Before the HasUuids trait existed, the standard pattern was to hook into Eloquent's model boot lifecycle:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Order extends Model
{
public $incrementing = false;
protected $keyType = 'string';
protected static function boot(): void
{
parent::boot();
static::creating(function (Model $model) {
if (empty($model->{$model->getKeyName()})) {
$model->{$model->getKeyName()} = (string) Str::uuid();
}
});
}
}
Str::uuid() wraps Symfony's UUID component and returns a UUID v4 value. The empty check ensures manually-set IDs aren't overwritten — useful for seeding or migrations where you need deterministic IDs.
Route Model Binding with UUID Primary Keys
Laravel's route model binding works without any changes to your route definitions:
// routes/api.php
Route::get('/orders/{order}', [OrderController::class, 'show']);
Route::put('/orders/{order}', [OrderController::class, 'update']);
Route::delete('/orders/{order}', [OrderController::class, 'destroy']);
// app/Http/Controllers/OrderController.php
class OrderController extends Controller
{
public function show(Order $order): JsonResponse
{
return response()->json($order);
}
}
Laravel resolves the {order} parameter by looking up id in the database. Since the column is now a UUID string, the lookup works exactly the same — you just pass a UUID in the URL instead of an integer: GET /api/orders/550e8400-e29b-41d4-a716-446655440000.
API Resources and UUID Casting
Eloquent returns UUIDs as strings in API resources. If you want to ensure consistent formatting in your JSON output:
// app/Http/Resources/OrderResource.php
class OrderResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id, // already a string, e.g. "550e8400-..."
'customer_email' => $this->customer_email,
'total' => $this->total,
'created_at' => $this->created_at->toISOString(),
];
}
}
No cast is needed — HasUuids stores and retrieves the UUID as a plain string. You can optionally add 'id' => 'string' to $casts for explicitness, but it doesn't change behavior.
Use the free UUID generator above to create test UUIDs for Tinker sessions, factory states, or Postman API tests.
Generate Test UUIDs for Laravel Factories and Tinker
The Cheetah UUID Generator produces RFC-compliant UUID v4 strings — copy them into Laravel factories, Postman collections, or database seeders instantly.
Open Free UUID GeneratorFrequently Asked Questions
Does HasUuids work with soft deletes?
Yes. The HasUuids trait is compatible with SoftDeletes — they can both be used on the same model without conflict.
Can I use UUID on a non-primary column with HasUuids?
Yes. Override the uniqueIds() method to return an array of column names that should receive auto-generated UUIDs. The primary key column is always included.
How do I generate a UUID in a Laravel factory?
Factories respect HasUuids automatically — you don't need to set the id in the factory definition. If you need a specific UUID for a test, use Order::factory()->create(['id' => 'your-uuid-here']).
Is Str::uuid() the same as UUID v4?
Yes. Str::uuid() internally uses Symfony's Uuid component and generates a version 4 (random) UUID compliant with RFC 4122.

