khalyomede/laravel-eloquent-uuid-slug
A Laravel package that adds UUID slug support to Eloquent models, automatically generating unique, URL-friendly identifiers for your records so you can reference them in routes and links instead of exposing numeric IDs.
Architecture fit. The package is a thin Eloquent trait that generates a UUID slug and exposes it as the model's route key, so it slots into Laravel's existing trait + implicit route model binding conventions rather than introducing a new abstraction layer. It is additive by design: you keep your integer/UUID primary key and foreign keys intact, and add a secondary slug column used only for public identification and lookup. That keeps blast radius small — no service provider gymnastics, no repository pattern, no change to query semantics beyond getRouteKeyName(). For a product whose only requirement is "don't expose sequential IDs in URLs," this is the minimal viable mechanism.
Integration feasibility. Work is concentrated in three places: a migration adding slug (unique index, eventually NOT NULL), the trait on each target model, and controller/route signatures that switch from Model $model resolved by id to resolution by slug. Existing rows need a backfill. No queue, cache, or external service dependency. A competent Laravel developer can prove this out in under a day.
Technical risk.
getRouteKeyName, model event boot order) rather than architectural mismatch.composer.json constraints against our Laravel/PHP floor before planning.slug to primary key. Also confirm whether collisions are retried or simply throw.slug is the same column name used by spatie/laravel-sluggable and similar packages. If we ever want human-readable slugs, this decision pre-empts it.insert()/upsert() and raw seeding skip model events, so slugs will be absent — a recurring source of production data gaps.Key questions for the maintainer / spike.
withTrashed, whereHas, and bulk operations?Stack fit. Best suited to a conventional Laravel monolith or JSON API that leans on Eloquent route model binding for public-facing resources — customer portals, shareable links, audit-friendly URLs. It is a poor fit if we already expose UUID primary keys (redundant), or if the identifiers are consumed by partners who expect stable, sortable, human-referenceable IDs.
Migration path. Sequential, reversible, zero-downtime:
slug column plus a unique index in a standalone migration.NOT NULL constraint and enable the trait per model.getRouteKeyName() and update controllers, policies, and tests.id-based routes alive behind a deprecation window.Because id remains the primary key, rollback is simply disabling the trait and reverting the route signatures — the column can stay dormant.
Compatibility considerations. Verify implicit binding behavior with soft deletes (withTrashed) and nested resources; check that whereHas/with eager loads still key correctly; ensure Laravel Scout indexes the slug if search-powered lookups exist; ensure API Resources emit slug and suppress id so clients don't regress to the old identifier. If Horizon/queues serialize models, confirm route() helper output in queued mailables updates.
Sequencing. Spike and fork-read the source first (>1 hour, resolves the key questions), then migrations/backfill, then per-model rollout starting with the lowest-traffic public resource, then documentation, then deprecation of id routes. Do not bundle this with an unrelated schema release — the backfill and constraint change want a quiet deploy window.
Maintenance. Effectively zero ongoing maintenance if we accept ownership. Given the dependency profile, I would vendor the trait into app/Models/Concerns and delete the Composer requirement. That converts an unmaintained third-party package into ~40 lines we control, and eliminates the upgrade-blocker risk at the next Laravel major.
Support. No vendor, no SLA, community-only issue tracker with a single maintainer. Internal ownership must be assigned explicitly; otherwise this becomes orphaned infrastructure that nobody understands during an incident.
Scaling. One additional unique-index write per model insert — negligible at our volumes. Read path is a single indexed string lookup, comparable to primary key resolution. Watch storage if we roll this across high-cardinality tables (36 bytes/row plus index); consider binary(16) or UUIDv7 only if the slug becomes a hot join key, which it should not.
Failure modes to design against.
NOT NULL post-backfill and add a factory state.Ramp-up. Under a day for a Laravel-fluent engineer, most of it spent on the backfill and route-signature sweep rather than the package itself. Add the trait convention to the team's new-model checklist and document "public identifier = slug, internal = id" in the API style guide so the pattern doesn't drift.
Recommendation. Adopt the pattern, not the dependency: implement the trait in-house, ship the migration and backfill, and revisit only if the public identifier requirement grows into human-readable slugs, at which point spatie/laravel-sluggable is the better-supported choice.
How can I help you explore Laravel packages today?