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

Ulid Laravel Package

ariby/ulid

View on GitHub
Deep Wiki
Context7

Getting Started

Install and inspect before you commit. With zero stars and zero dependents, treat the package source as the documentation.

composer require ariby/ulid

Laravel auto-discovery should register the service provider. If the README mentions a config file:

php artisan vendor:publish --provider="Ariby\Ulid\UlidServiceProvider"

Where to look first — read these three things in vendor/ariby/ulid/src/ before writing any model code:

  1. The trait name (commonly HasUlid / UsesUlid / HasUlids) and which model events it hooks (creating, saving).
  2. Whether it sets $keyType = 'string' and $incrementing = false for you, or expects you to declare them.
  3. Which generator it wraps — Symfony\Component\Uid\Ulid, Str::ulid(), or a bundled implementation. This determines whether you can swap it later.

Migration side — you still own the schema:

Schema::create('orders', function (Blueprint $table) {
    $table->ulid('id')->primary();          // char(26) on MySQL, native uuid type on Postgres
    $table->foreignUlid('customer_id')->constrained();
    $table->timestamps();
});

First use case — a model whose IDs appear in URLs or APIs and where you want sortable, non-enumerable keys without a UUID's index-unfriendly randomness:

use Ariby\Ulid\HasUlid;

class Order extends Model
{
    use HasUlid;

    protected $keyType = 'string';
    public $incrementing = false;
}
$order = Order::create([...]);
// "01J8Z9K2M3N4P5Q6R7S8T9V0W1"

Sanity-check the trait actually fired in tinker: Order::create([])->getKey() should be a 26-char Crockford base32 string, not null or an int.

Implementation Patterns

Primary key replacement (the intended path). Use the trait on the model plus $table->ulid('id')->primary(). Every insert generates the ULID in the creating hook, so Model::create() and new Model(); $m->save() both work. Model::make() does not — the key is null until persisted, so don't build URLs from an unsaved instance.

Secondary "public" key (safest incremental adoption). Keep the bigint PK for foreign keys and clustered-index performance, and add a ULID column for external exposure:

Schema::table('orders', function (Blueprint $table) {
    $table->ulid('public_id')->unique()->after('id');
});
protected static function booted(): void
{
    static::creating(fn ($m) => $m->public_id ??= (string) Str::ulid());
}

public function getRouteKeyName(): string
{
    return 'public_id';
}

This is usually the right call for large tables — see the Gotchas section on InnoDB.

Route model binding and API resources. ULIDs are strings, so binding works unchanged once getRouteKeyName() points at the ULID column. Cast explicitly in resources so JSON emits a string, never a numeric-coerced value:

public function toArray($request): array
{
    return [
        'id' => (string) $this->id,
        'created_at' => $this->created_at->toIso8601String(),
    ];
}

Relations and foreign keys. Use foreignUlid() on the child table and declare the type on both sides. Because ULIDs are non-sequential-looking but time-sortable, Order::latest('id') returns chronological order for free — you can drop many orderBy('created_at') clauses, though keep an explicit created_at index if you also filter by date ranges.

Factories. Never hardcode a ULID. Let the model generate it:

Order::factory()->create();          // trait supplies id
Order::factory()->create(['id' => (string) Str::ulid()]);  // only when you need a known value

If your factory defines 'id' => Str::ulid() explicitly, you'll mask a broken trait — remove it during testing to prove the trait works.

Mixed model set. You can adopt ULIDs table by table. A shared base model or trait keeps it consistent:

abstract class UlidModel extends Model
{
    use HasUlid;
    protected $keyType = 'string';
    public $incrementing = false;
}

Laravel 9+ note. Illuminate\Database\Eloquent\Concerns\HasUlids ships in core and does exactly this. If you're on 9.x or later, benchmark the package against the built-in before adding a dependency — the migration path is a one-line trait swap, and Str::ulid() covers non-model uses.

Gotchas and Tips

Verify the trait sets $incrementing = false. If it doesn't, MySQL will attempt to insert 0 / auto-increment behaviour on a char(26) column and you'll get silent truncation or a duplicate-key error on the second row. Explicitly declaring public $incrementing = false; and protected $keyType = 'string'; is cheap insurance regardless of what the package claims.

Clustered-index cost on InnoDB. Random-ish string primary keys bloat the clustered index and cause page splits; ULIDs are timestamp-prefixed so they're far better than UUIDv4, but still worse than bigIncrements. On tables that will exceed a few million rows, prefer the bigint-PK + ULID-public-column pattern above.

Time leakage. The first 10 characters encode a millisecond timestamp. Anyone holding an ID learns when the row was created and, by sampling, roughly how many rows exist. If that's sensitive, generate IDs in batches or don't expose raw ULIDs.

Monotonicity within a millisecond. Standard ULIDs sort by timestamp, but two IDs generated in the same millisecond sort randomly relative to each other. If you need strict insertion order, check whether the package uses Symfony's monotonic factory — and don't rely on ORDER BY id for exact sequencing anyway.

String collation. Store as char(26) or the native ulid/uuid type on Postgres, never varchar with an ambiguous collation. ULIDs are uppercase Crockford base32; if any code path lowercases them (URL slugs, user input), normalize on lookup — MySQL's default utf8mb4_unicode_ci will match case-insensitively and hide the bug, Postgres will not.

find() with integers. ULIDs are strings. Model::find(123) will silently return null rather than erroring. Type-hint route params and casts as string.

Don't let users supply the ID. If id is in $fillable, mass assignment from a request body overrides the generated ULID. Keep id out of $fillable and use $model->forceFill() only in seeders/imports.

Debugging a null key. If inserts fail with a not-null violation, check in order: (1) trait is actually imported on the model, (2) trait isn't being overridden by an HasUlids/bootHasUlid name collision, (3) the model's creating listener isn't unset by a parent class, (4) the column exists in the migration (Schema::hasColumn). A quick static::creating(fn() => dump('fired')) in tinker isolates it.

Testing assertions. Compare with $this->assertTrue($a->id === $b->id) rather than string-equality against a literal. For ordering, generate with usleep(1000) between rows to guarantee distinct millisecond prefixes.

Maintenance risk. Zero stars and zero dependents means no community bug reports and no guarantee of Laravel 12 compatibility. Two mitigations: pin the version in composer.json, and wrap generation in your own interface so a future swap to HasUlids or a custom booted() hook is a single-file change:

interface IdGenerator { public function generate(): string; }

Extension point. If the package exposes a config for a custom generator class, bind your own implementation that adds a prefix (e.g. ord_ + ULID) or delegates to Symfony's monotonic factory. If it doesn't expose one, the model's creating event is your override point — hook it after the trait registers, and only assign $model->id when it's null so you stay idempotent.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel