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

jamesh/laravel-uuid

Adds UUID primary keys to Laravel Eloquent models. A lightweight trait generates a UUID on creation, supports lookups by UUID, and makes models route-key ready, so you can replace auto-incrementing IDs without rewriting your queries or controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

Step 0 — check compatibility before you install. The package is archived (last release Feb 2022), has 20 stars and zero dependents. Open its composer.json and confirm the illuminate/database / illuminate/support constraints cover your Laravel version. It targets Laravel 6–8. If you are on Laravel 9+, stop here — Illuminate\Database\Eloquent\Concerns\HasUuids ships in core and does the same job (plus ordered UUIDs and uniqueIds() support for secondary columns). Installing a two-year-old abandoned trait on a modern app is a net negative.

If you are on Laravel 6–8:

composer require jamesh/laravel-uuid

No service provider config, no publishable config file — the whole package is one trait. Spend your first minute reading it:

cat vendor/jamesh/laravel-uuid/src/HasUuid.php

It is ~30 lines and boils down to three things: getIncrementing() => false, getKeyType() => 'string', and a bootHasUuid() hook that fills the key with a UUID string inside the creating event. Knowing that is 90% of understanding the package.

Wire up a model. The migration must declare the key as a UUID before the model works:

Schema::create('posts', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('title');
    $table->timestamps();
});
use JamesH\Uuid\HasUuid;

class Post extends Model
{
    use HasUuid;

    protected $guarded = [];
}

First use case — verify end to end in tinker:

$post = Post::create(['title' => 'Hello']);
$post->id;        // "9c1f0e2a-..."  (string, populated before INSERT)
$post->incrementing; // false
$post->getKeyType(); // "string"

If id is null after create(), the creating listener isn't firing — usually because you called Post::insert() or a query-builder insert instead of create().

Implementation Patterns

New tables. Use $table->uuid('id')->primary() for every model that adopts the trait, and $table->foreignUuid('post_id')->constrained() for relations. Mixing $table->id() and $table->uuid('id') across tables is fine — the trait is opt-in per model, not global.

Relations and pivot tables. Foreign key columns must be uuid, never unsignedBigInteger. Pivot tables need $table->uuid('post_id') on both sides plus a composite primary key. Any morphs()/nullableMorphs() needs the manual variant (uuidMorphs('commentable') exists in Laravel 7+; on Laravel 6 write the two columns yourself).

Route model binding. Works unchanged because the key is a string; Route::get('/posts/{post}') resolves via whereKey($uuid). Keep getRouteKeyName() as id. Bind with scoped bindings ({post:uuid} is redundant, but ->scopeBindings() is worth it for nested resources).

Reusing one concern across a codebase. Don't scatter use HasUuid blindly — wrap it in your own base model or a local UsesUuidKey trait that composes the package trait. That gives you one seam to swap in Laravel's native HasUuids later without touching 40 models:

trait UsesUuidKey
{
    use \JamesH\Uuid\HasUuid;
    // future: use \Illuminate\Database\Eloquent\Concerns\HasUuids;
}

Custom UUID flavour. If you need ordered UUIDs (strongly recommended for PK performance — see below), override the generator in your model rather than forking the package:

protected static function bootHasUuid()
{
    static::creating(function ($model) {
        if (empty($model->{$model->getKeyName()})) {
            $model->{$model->getKeyName()} = (string) Str::orderedUuid();
        }
    });
}

Defining bootHasUuid() in the model shadows the trait's version — that's the intended extension point and it's cleaner than editing vendor code.

Factories and seeders. Post::factory()->create() gets a UUID automatically. Bulk seeders that call Post::insert([...]) do not — they bypass Eloquent events and hit a NOT NULL violation. Loop over create(), or generate Str::uuid() explicitly in the array you pass to insert().

API layer. Cast nothing special: $post->id is already a string, so JsonResource and toArray() emit it correctly. Cache keys built from $post->id work, but remember they're opaque — don't build anything on ordering. Use created_at (or an ordered UUID) for sorting.

Retrofitting an existing integer-keyed model is the hard case and usually not worth it: you need a column swap, backfill (Str::uuid() per row), FK rewrites on every referencing table, and a deploy window. If you must, treat it as a full data migration, not a trait change.

Gotchas and Tips

  • Abandoned is the headline gotcha. No fixes for Laravel 10/11, PHP 8.2+ deprecations, or security issues. Treat this as vendored code you own. For anything new, use Illuminate\Database\Eloquent\Concerns\HasUuids (Laravel 9+) or generate the UUID in a creating observer yourself — it's eight lines.
  • Trait name collision. If you ever use both the package trait and Laravel's native HasUuids, you get a "trait method collision on bootHasUuids" fatal. Pick one; alias if you're mid-migration.
  • make() has no ID. The creating event only fires on save. Post::factory()->make()->id is null — assert on IDs only after create(), or use factory()->create() in tests that need them.
  • Raw inserts bypass everything. DB::table('posts')->insert(), upsert(), and insertOrIgnore() skip model events and will insert NULL keys. Same for Model::withoutEvents(...). If you use those deliberately, supply the UUID yourself.
  • Random UUIDv4 wrecks B-tree locality. Each insert lands on a random index page: write amplification, buffer-pool churn, and a bloated PK index. On high-write tables use Str::orderedUuid(). This is the single biggest reason to prefer Laravel's native trait, which uses ordered UUIDs out of the box.
  • Storage size. uuid is char(36) — 36 bytes per key, replicated into every secondary index and every FK column. If a table is index-heavy, consider binary(16) with a cast; this package does not support that, so it's a manual change.
  • Migration order. A foreignUuid(...)->constrained() will fail if the parent table hasn't been created yet, or if the parent uses $table->id() (bigint) — MySQL will error on type mismatch; SQLite may silently let it through and fail at query time.
  • Debugging the key generation. Add a temporary listener after the trait's and log $model->getKeyName() / $model->getKey(). If another creating listener (e.g. a slug package, or an override in a child model) writes to the key first, the trait's empty() guard will skip generation and you'll get a partial/odd value. The guard only checks emptiness, so any earlier listener wins.
  • Don't put id in $fillable. The trait fills it; allowing mass assignment of id lets a client choose its own key.
  • $incrementing and $keyType must be set together. If you override one in your model, set both — a string key with $incrementing = true produces "1", "2", "3" style IDs that look like UUIDs to nobody and break sorting assumptions.
  • Route binding returns 404, not a validation error, for malformed UUIDs — whereKey() just finds nothing. If you need a 400, add a route-level whereUuid() constraint.
  • Migration path off the package is painless on the data side. Both traits produce 36-char strings, so you can swap the trait on a live schema. Behaviour changes: native emits ordered UUIDs, so new rows sort lexicographically while old rows don't. Never rely on orderBy('id') across the boundary — order by created_at.
  • Testing edge case: older MySQL/SQLite drivers store uuid as char(36) and are case-insensitive in comparisons; whereKey(strtoupper($id)) may match, which can mask normalization bugs. Store and compare lowercased strings consistently.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib