eig/eloquent-uuid
A Laravel package that lets Eloquent models use UUID primary keys instead of auto-incrementing integers. Drop it into existing models to generate unique, non-sequential IDs automatically for safer, distributed-friendly records.
Architecture fit. The package is a single Eloquent trait (~30–40 lines) that hooks the creating model event, assigns a UUID to the primary key, and flips $incrementing = false / $keyType = 'string'. That is the idiomatic Laravel extension point — it composes into any model without touching schema, routing, or serialization, and it keeps route-model binding working because the PK is the route key. From a product-architecture standpoint the package does not decide anything meaningful: it implements a decision (opaque public identifiers) that TPMs must make anyway. Its scope is narrow by design, and it explicitly does not address storage format (char(36) vs binary(16)), index locality, ordered IDs (ULID/UUIDv7), or database-level defaults. Those remain separate architecture calls.
Integration feasibility. Constrained. Release 3.0.0 pins PHP 8.0+, illuminate/* 9.x, and a transitive runtime dependency on eig/UUID 2.5.x. On Laravel 10/11, Composer either refuses resolution or installs something never tested against the framework — treat that as disqualifying, not as "probably fine." The addressable window is narrow: Laravel 9 is exactly the baseline where Laravel already ships HasUuids and HasUlids in Illuminate\Database\Eloquent\Concerns. The package's residual value is trait ergonomics and a forkable reference implementation, not capability.
Technical risk. High relative to the value delivered:
HasUuids/HasUlids produces two creating hooks racing on id and competing $keyType assignments. Silent, hard-to-diagnose corruption of the model contract.Model::insert(), upsert(), query-builder writes, raw seeders, and raw SQL bypass creating entirely — producing NULL keys and broken foreign keys. The #1 silent failure mode.The 0.595 score and thin adoption signal should be read as insufficient evidence of quality, not as evidence of poor code — but for a TPM, insufficient evidence plus an abandoned maintainer plus an EOL framework is a decision, not a question.
Key questions to resolve before approving:
insert()/upsert()/query-builder instead of Model::create? (Then the trait will silently emit NULL keys.)char(36) acceptable on every secondary index and FK, or do we need binary(16) casting?Stack fit. Only consider under a hard constraint: Laravel 9.x / PHP 8.0+, existing investment in eig/UUID 2.x, greenfield bounded context, single id primary key, all writes through Eloquent. Apply it deliberately to 3–5 truly public-facing models (orders, invoices, api_tokens, share_links, webhooks) — not grep-and-apply across the codebase. Join tables, audit logs, jobs, and cache should keep integer PKs.
Recommended default: bypass the package. For any target other than the narrow constraint above, the honest answer is:
HasUuids (or HasUlids for ordered, index-friendly IDs). Zero net-new dependencies, covered by framework tests, patched with framework releases.uuid column with getRouteKeyName() returning 'uuid', and generate via Str::uuid() in a booted() hook. This avoids FK rebuilds entirely.id PK, or UUID on a non-PK column → fork the ~40-line trait into App\Concerns. This simultaneously removes the eig/UUID dependency, the Laravel 9 ceiling, and the maintainer risk. Usually the right call.Compatibility controls if adopted. Pin exactly ("3.0.*"), add both eig/eloquent-uuid and eig/UUID to the dependency inventory, and verify behavior against 3.0.0 — not against 2.x-era guidance. The 2.x → 3.0 jump changed PHP/Laravel floors, the transitive dependency, and possibly trait name and key-type handling. Copy the trait FQN from the vendor directory, not from a blog post.
Sequencing.
getKeyType() === 'string' and getIncrementing() === false, confirm relations and JSON serialization behave.use HasUuid;. Without this, the next contributor applies it everywhere.HasUuids or fork the trait — scheduled, not "later." Treating the Laravel 9 ceiling as a forcing function is the only responsible condition of adoption.getIncrementing() and getKeyType() in the test suite, not just the ID format. A framework or eig/UUID bump can break those without breaking the ID itself.Maintenance. Two packages from one maintainer, last shipped December 2022, zero dependents to act as a canary. Every Laravel major bump becomes a manual compatibility investigation against an unsupported library, with no upstream test suite to lean on. The realistic maintenance posture is "vendor or fork it," which converts a dependency decision into an ownership commitment — someone on the team must own the ~40 lines.
Support. Effectively none. No community, no security response path, no release cadence. The source of truth is the vendor directory. This is a meaningful operational cost not visible in the adoption cost: debugging falls to whoever can read the trait, and that knowledge is tribal unless documented in the ADR.
Scaling. Two distinct concerns:
$table->uuid() maps to char(36) on MySQL — 36 bytes on every secondary index and every FK, amplified across join tables. binary(16) is the answer if write volume or index size matters, but it requires cast handling at the model layer and must be decided before schema freeze.HasUlids or UUIDv7 is the pragmatic move. The package does not solve ordering locality; adopting it for a write-heavy table is a scaling decision made by accident.Failure modes to instrument and guard against:
Model::insert(), upsert(), query-builder seeders, and raw SQL bypass the creating event. Mitigation: a DB-level default where supported, plus a Str::uuid() fallback inside import and seeder paths, plus a schema NOT NULL constraint to fail loudly rather than propagate broken FKs.incrementing = true. Mitigation: a debug assertion (getIncrementing(), getKeyType(), getKeyName(), toArray()['id']) in the test suite for every model that uses it.HasUuids/HasUlids produces competing creating hooks and key-type assignments. Mitigation: an explicit "one UUID mechanism per model" rule, enforced in code review.if (! $model->getKey()), or bypass the trait on that path.binary(16) PKs break silently.Ramp-up. Low in absolute terms — the trait is readable in one sitting and forkable in an hour. The real onboarding cost is the surrounding knowledge: why only three models carry it, why bulk writes need a fallback, why char(36) was chosen, and what the exit plan looks like. That cost is paid once if the ADR exists and repeatedly if it doesn't. Budget the ADR and a short test assertion as part of the integration, not as follow-up work.
Bottom line for roadmap planning. This package does not buy capability — it buys a small amount of typing in a configuration where Laravel already provides the capability natively, at the cost of an unsupported framework floor and two unattended dependencies. The valuable decision it supports (opaque public identifiers) should be made on product and architecture grounds, then implemented with native HasUuids/HasUlids or a five-line in-house trait. Approve only as a validated spike on a greenfield Laravel 9 context, with a fork-or-native migration item on the roadmap from day one.
How can I help you explore Laravel packages today?