dmhall0's avatar

ULID Experiences & Opinions

What has been everyone's experiences and/or opinions on using ULID as the primary key in Laravel? I am highly considering it for my project for things like users, teams, etc; but I have no experience nor enough knowledge to say whether its good or bad. What are your thoughts?

0 likes
18 replies
tisuchi's avatar

@dmhall0

Here is my point about ULID.

It has some advantages as a primary key, including:

  • ULIDs are unique: They are generated using a combination of a timestamp and random data, so it is extremely unlikely that two ULIDs will be the same.

  • ULIDs are lexicographically sortable: This means that they can be sorted in order based on the characters in the ULID. This can be useful if you need to sort records by the time they were created.

  • ULIDs are efficient: They are much shorter than UUIDs (Universally Unique Identifiers), which can be useful if you have a lot of records and need to store the primary key in a database column with a limited size.

Overall, ULIDs can be a good choice for a primary key in a database, especially if you need to sort records by the time they were created or if you are concerned about efficiency. However, it is important to consider your specific needs and requirements when choosing a primary key for your project.

5 likes
dmhall0's avatar

@tisuchi Thanks for the feedback. Your listed advantages are what attracted me to ULID's.

martinbean's avatar
Level 80

@dmhall0 Don’t. By all means, add ULIDs as a second column to your table, but nothing is as performant as integers for your primary and foreign keys.

2 likes
Tray2's avatar

I would use neither of them, I would stick to regular auto incrementing integer columns.

The main reasons are:

  • indexable
  • 100% unique in your table
  • Low space usage

I know that in some cases it's a good thing to use a more random key, however I would in that case just use them to make it harder to guess the id of something. I would however still use the integer id for all constraints and relations, and only use the uuid or ulid in places where it is visible to the user.

1 like
martinbean's avatar

Just to be clear: my suggestion isn’t to not use ULIDs at all. Just don’t use them as primary keys.

dmhall0's avatar

@martinbean Totally understand. I like integer primary keys as it does make things easier to "read" if you will; and I will have a lot of foreignKeys on them so keeping it small would be better. I think I am going to use ULID's as a secondary, publicly visible id (like a slug), and keep the primary key as autoincrementing integers. Thanks again!

4 likes
dmhall0's avatar

Maybe a dumb question... on the creation of a record, how can I generate a ULID in another non-primary key column?

dmhall0's avatar

@click Thanks, but how do I generate the ULID of '1234'? The ULID field in my table is public_id. Upon creation of a record how do I generate a ULID (e.g. 01GP8ZFFT9ZMWT6N5GPHPR58SH)

dmhall0's avatar

I did this...

protected static function boot()
    {
        parent::boot();

        static::creating(function ($query) {
            $query->public_id = Ulid::generate(now());
        });
    }

Seems to work correctly.

martinbean's avatar

@dmhall0 Laravel has its own HasUlids trait for creating ULIDs: https://laravel.com/docs/9.x/eloquent#uuid-and-ulid-keys

By default, yes, it assumes you want to use ULIDs for the primary key, but you can override this assumed behaviour and add a method to your model to tell it you want to use ULIDs for a second column instead:

class Foo extends Model
{
    use HasUlids;

    public function uniqueIds()
    {
        // Tell Laravel you want to use ULID for your secondary 'ulid' column instead
        return [
            'ulid',
        ];
    }
}

how do I generate the ULID of '1234'?

You don’t. A ULID is an opaque string. It it not a “hash” or related to any other value in any way. Like a UUID, a ULID is just a unique value, made up of alphanumeric characters.

4 likes
deadrabbits's avatar

if I choose ULID as second key (published/exposed key) is better than UUID? how the performance?

Tray2's avatar

@deadrabbits Next time, create a new thread. ULID is a little better performance wise, than UUID, but I still wouldn't use it for a primary key, Integers are still faster.

min-mahatara's avatar

Note: I used ChatGPT to help structure parts of this explanation, but the approach described is from my actual production experience.

Laravel 12.x supports UUIDv7 out of the box (and Laravel 11.x introduced it). However, if you want to use both UUID7 and ULID together on the same model, you'll need a custom solution.

✅ Internal vs. External Identifiers

Using UUIDv7 for primary keys (e.g., id) is an excellent choice—especially for scalable applications. UUIDs are globally unique and make tasks like data merging and system interoperability much safer by minimizing the risk of key collisions.

One of the key advantages of using UUIDs as internal identifiers is that you can perform direct database operations without relying solely on APIs. This encourages cleaner, more decoupled API design.

However, UUIDs (especially v7) are long and not user-friendly. Therefore, I strongly recommend not exposing UUIDs to end users. Instead, use ULIDs for public-facing IDs.

For example:

  • Use id (UUIDv7) internally for primary keys, foreign keys, and unique constraints.
  • Use public_id (ULID) externally in URLs, APIs, and UI displays.

This separation protects internal infrastructure and keeps your system's internals abstracted from external consumers.

🛠 CRUD Operations and Routing

Always expose and interact with the public_id for all CRUD operations. This adds a slight abstraction layer but helps shield internal identifiers and improves system design.

Laravel allows you to customize route model binding to use public_id:

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

⚖️ Performance Considerations

It's true that UUIDs and ULIDs can be slightly slower than auto-incrementing integers due to their size and index performance. However, the benefits—global uniqueness, scalability, and flexibility—far outweigh these drawbacks, especially for distributed systems.

UUIDv7 offers the additional benefit of lexical sortability. Because it encodes a timestamp in milliseconds, newly generated UUIDs are naturally ordered. This improves index locality and query performance compared to UUIDv4.

🔄 Data Portability

Another major benefit of UUIDs: they're ideal for syncing data across environments or services. Since UUIDs are unique across systems, there's very little risk of collision when importing or migrating data.


✅ Recommended Strategy

  • Internal IDs (id, FK, PK, UK): Use UUIDv7. Never expose.
  • External IDs (public_id): Use ULID. Always expose.

🧩 Custom Trait: HasUuidsAndPublicId

Since Laravel does not currently support using both HasUuids and HasUlids traits together in a single model, you can define a custom trait to achieve this:

Use this trait on your models:

class User extends Model
{
    use HasUuidsAndPublicId;

    protected $table = 'users';
}

⚠️ Note: Trying to use both HasUuids and HasUlids traits simultaneously, like this:

use HasUuids, HasUlids;

will not work, as Laravel does not currently support combining both traits on the same model.


Final Thoughts

For scalable Laravel applications:

  • Use UUIDv7 for internal keys.
  • Use ULIDs for external identifiers.
  • Encapsulate this logic in custom traits like HasUuidsAndPublicId.

This strategy balances performance, security, and maintainability, and is already adopted by many large-scale systems.

1 like

Please or to participate in this conversation.