Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Uuid Laravel Package

nevadskiy/laravel-uuid

A Laravel package that adds UUID support to Eloquent models, making it easy to use universally unique identifiers as primary keys. It provides traits and helpers for generating and assigning UUIDs automatically, simplifying setup and integration with existing models.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and inspect the source first.

composer require nevadskiy/laravel-uuid
ls vendor/nevadskiy/laravel-uuid/src

The package is intentionally tiny — one trait, no service provider config to speak of. Before writing a line of application code, open src/ and read the trait top to bottom (it is usually <100 lines). You want to confirm three things: the trait's fully-qualified name (e.g. Nevadskiy\Uuid\HasUuid — namespace may include Concerns), the boot method name (bootHasUuid), and the method it calls to generate the value (generateUuid() / Str::uuid()). Everything else in this assessment follows from those three facts.

2. Choose your key strategy before touching migrations.

Two shapes are common, and the package supports the first out of the box:

  • UUID as primary key — $table->uuid('id')->primary(), no auto-increment column at all.
  • Auto-increment PK + UUID as a public identifier — integer id stays internal, a uuid column is exposed in URLs/APIs.

Decide now; retrofitting a primary key on a table with data is painful.

3. First use case: a users table with UUID keys.

// migration
Schema::create('users', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamps();
});
// app/Models/User.php
use Illuminate\Database\Eloquent\Model;
use Nevadskiy\Uuid\HasUuid; // verify FQCN in vendor/

class User extends Model
{
    use HasUuid;

    protected $guarded = [];
}
$user = User::create(['name' => 'Ada', 'email' => '[email protected]']);
$user->id;            // "9b2f..." — generated on the `creating` event
User::find($user->id); // works unchanged; whereKey() already handles the string

Route model binding, findOrFail, whereKey, and API resources all work with no further changes — Eloquent only cares that $keyType is string and $incrementing is false, which the trait sets for you.

Implementation Patterns

Apply the trait at the base model, not per-model. If every model gets a UUID, put the trait on an abstract App\Models\Model (or a shared BaseModel) so new models inherit the behaviour by default and nobody forgets it.

Keep factories ignorant of the key. Factories should never set id. Because the value is generated on creating, User::factory()->create() just works; ->make() returns a model with a null key until persisted. If a test needs a deterministic key, pass it explicitly and the trait should defer to the provided value (verify this — most implementations do a ??= style assignment; if yours overwrites, that's a bug to patch locally or guard with Model::withoutEvents).

Foreign keys must match the type exactly. With UUID PKs, use foreignUuid() (Laravel 8+) or $table->uuid('user_id'); a unsignedBigInteger FK will fail at migration time, and worse, a char(36)/varchar(255) mismatch will silently break index usage on MySQL. Keep collation and length identical on both sides.

Related-model creation is the sweet spot.

$order = $user->orders()->create([...]);  // FK filled from the UUID parent

Pivot tables (user_id, role_id) all become uuid columns — worth a single pass over database/migrations early rather than discovering it one failing test at a time.

Wrap UUIDs at the boundary. Cast/validate incoming route or request values with the uuid validation rule before they hit the query builder, so a malformed 36-char string doesn't reach MySQL and produce a driver-level error:

$request->validate(['user' => ['required', 'uuid']]);

Expose both keys when you keep an integer PK. If you chose the "public UUID" pattern, add getRouteKeyName(): string { return 'uuid'; } on the model and $table->uuid('uuid')->unique() — this gives you fast clustered inserts plus opaque URLs, and is the pattern most teams actually want for user-facing resources.

Ordered UUIDs for high-volume tables. Random v4 keys fragment InnoDB's clustered index on insert. For append-heavy tables (events, logs, messages), check whether the package exposes an ordered variant or override the generator to emit a time-ordered UUID (v7). The trait's generateUuid() is the extension point — overriding it in your model is a one-liner and keeps the rest of the package's wiring intact.

Gotchas and Tips

Laravel 9+ already ships HasUuids. Illuminate\Database\Eloquent\Concerns\HasUuids (and HasUniqueStringIds in 10+/11+) covers most of this ground, including newUniqueId() and uniqueIds(). The honest assessment: on a modern Laravel app this package is largely redundant. Its value is on Laravel 6/7/8, or as a 40-line readable reference if you'd rather vendor the behaviour into a base model. If you adopt it on Laravel 10+, do not also use HasUuids — the two traits both define key-type/incrementing behaviour and will fight over the boot events.

Trait boot methods are name-sensitive. bootHasUuid only fires if the trait is literally named HasUuid. If you alias it (use HasUuid { ... }) or wrap it in your own trait, the boot hook silently never registers and you get Integrity constraint violation: id cannot be null on insert. Symptom to recognise: everything works in make() tests, nothing works on create().

A custom boot() on the model can break it. If a model overrides static::boot() and forgets parent::boot(), initialisation of trait hooks stops. Same for models that override booted().

$incrementing/$keyType set by the trait are easily clobbered. Declaring public $incrementing = true; on the model (or a parent) after the trait can win depending on declaration order. If save() returns the model with a numeric id, check these two properties first with dd($model->getKeyType(), $model->getIncrementing()).

Debugging "the UUID is empty": in order, check (1) trait is actually used on the model, (2) boot hook registered — User::getEventDispatcher()->hasListeners('eloquent.creating: App\Models\User'), (3) $guarded/$fillable isn't blocking if you pass a value, (4) you aren't inside Model::withoutEvents(). Seeding and bulk insert() bypass model events entirely — DB::table('users')->insert([...]) will not generate a UUID, and will fail on a NOT NULL PK. Use Model::create() in seeders, or generate keys manually in bulk inserts.

firstOrCreate and updateOrCreate still need real columns. They compare on your lookup attributes, not on id, so they behave normally — but the create branch is where the UUID is minted. Beware updateOrCreate with a UUID-shaped id in the attributes array; it becomes an explicit key assignment.

Soft deletes, upsert, and insertOrIgnore all skip events. Anything that doesn't instantiate a model is outside the trait's reach.

Testing tip: assertDatabaseHas('users', ['id' => $user->id]) is fine, but avoid hardcoding UUIDs in test fixtures — use Str::uuid()->toString() or capture the model's key. Also cast uuid columns in $casts only if you want a UuidInterface; the default string behaviour is what route binding expects.

Performance reality check: 36-byte string PKs make every secondary index wider and every FK join slower than a bigint. At small-to-medium scale it's irrelevant; past tens of millions of rows, the "integer PK + unique UUID column" pattern is the pragmatic escape hatch, and it costs you nothing to adopt from day one.

Extension point: rather than editing vendor/, subclass or override generateUuid() on the model, or bind your own generator if the package resolves one from the container. Keep any local patches in a single trait so a future swap to Laravel's native HasUuids is a one-line change per model.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox