brokeyourbike/uid-keys
Laravel package that swaps auto-incrementing integer primary keys for unique UID keys on your Eloquent models. Add the trait and new records get collision-resistant string IDs automatically—ideal for APIs, distributed systems, and hiding sequential IDs.
Architecture fit. The package addresses a narrow, well-understood concern: swapping the primary-key strategy of Eloquent models from auto-incrementing integers to UUIDs or ULIDs. Idiomatic Laravel implementations of this are trait-based (overriding getIncrementing(), getKeyType(), and hooking creating/boot to assign the key), which is additive and low-coupling — it does not alter the query builder, routing, or serialization layers. That makes it architecturally safe in principle: it composes with existing models rather than imposing a base class or repository layer. The risk is not architectural, it is duplicative: Laravel 9+ ships HasUuids and HasUlids in Illuminate\Database\Eloquent\Concerns, covering the same ground with framework-level support and no third-party dependency. For any application on a currently supported Laravel version, the package's core value proposition is largely redundant.
Integration feasibility. Mechanically trivial — a Composer require plus one trait per model. The real cost sits outside the package: schema changes, foreign-key type changes, route-model-binding behavior, and API contract shifts (string keys vs. integer keys). Those costs are identical whether we use this package or the native traits, so the package choice itself is not the gating factor. Verify before committing: Laravel and PHP version constraints in composer.json; whether it depends on symfony/uid and at what version; whether it uses ordered UUIDs (v7-style, index-friendly) or random v4; and whether it publishes a config file or migrations.
Technical risk. High relative to the value delivered, driven almost entirely by project health rather than code quality:
Key questions.
uuid/binary(16) on Postgres/MySQL)?creating event only — i.e., does DB::table()->insert(), bulk insert, or insertOrIgnore bypass generation entirely?HasUuids/HasUlids trait already satisfy the requirement? If yes, this evaluation terminates with a "no adopt."Recommendation. Default to framework-native traits on Laravel 9+. Adopt this package only if we are pinned to Laravel 8 or earlier and need ULID support that the framework does not provide, or if we need a uniform trait interface across a mixed-version fleet. In either case, treat it as a thin, pinned, vendorable utility — not a strategic dependency.
Stack fit. Fits all standard Laravel deployments (MySQL, PostgreSQL, SQLite). Two fit considerations drive real work:
CHAR(26) are compact; UUIDs as CHAR(36) are not. On PostgreSQL prefer native uuid; on MySQL 8 prefer BINARY(16) with uuid_to_bin/bin_to_uuid conversion, or accept CHAR(36) and budget the index growth. Confirm the package is agnostic here — it almost certainly delegates storage decisions to our migrations, which means the decision is ours, not the package's.Migration path. Do not attempt an in-place PK swap on existing tables. Use an expand/contract sequence:
uuid/ulid column with a unique index alongside the existing integer PK.getRouteKeyName) and API serialization to the new column.Tables with high foreign-key fan-in (users, accounts, tenants) should be last, or never converted.
Compatibility. Audit for integer-key assumptions before writing any code: Filament/Nova resources, Spatie packages, queue payload serialization, cache keys, analytics/warehouse ingestion, mobile clients parsing IDs as int, and any raw SQL with whereIn('id', [...]) on untyped values. Also confirm Model::find() callers tolerate string keys and that json responses change shape — this is a breaking API change, not an internal refactor.
Sequencing.
composer.lock-freeze; vendor or mirror the source internally.Maintenance. Near-zero day-to-day effort once integrated — it is a small, stable concern. The real maintenance burden is upstream risk: no committed maintainer, no release cadence guarantees, and a plausible abandonment horizon. Mitigate by pinning the version, mirroring the repository internally, and treating an in-house fork (MPL-permitted, with license retention) as a pre-approved contingency. Budget roughly half a day per Laravel major upgrade to verify the trait still composes with framework changes to getKeyType/getIncrementing.
Support. Community-only, effectively self-support. There is no SLA, no vendor, and at 0 stars, no meaningful pool of prior adopters to draw incident experience from. Any production issue is ours to diagnose. This is acceptable for a utility of this size and unacceptable if ID generation ever becomes load-bearing for correctness across many services.
Scaling. Identifier choice, not the package, determines scaling characteristics:
CHAR(36) UUIDs are ~2–4× the index footprint of BIGINT; on tables in the hundreds of millions of rows this is a real capacity and memory decision. Use BINARY(16) or native uuid types where the database supports them.Failure modes.
insert() / upsert() / raw SQL skip model events, producing rows with null or empty keys. Highest-probability real defect; add a DB-level NOT NULL and unique constraint so it fails loudly.whereKey mismatches and 404s in route binding.int to string in JSON breaks typed clients, mobile apps, and downstream consumers.Ramp-up. Engineering ramp-up is minimal — one trait line, one line in the model. The organizational ramp-up is the actual cost: the team must internalize why an opaque key is being exposed, how route binding resolves it, why bulk operations need explicit ID assignment, and why integer keys may still exist internally. Plan a short design note and one working session; schedule the migration work as a distinct epic rather than an incidental refactor, because the schema and API blast radius dwarfs the integration effort.
How can I help you explore Laravel packages today?