ULID: The Engineer's Guide to Sortable Identifiers
ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier format designed to combine a timestamp with random data. Its canonical representation is a 26-character Crockford Base32 string that can be sorted lexicographically by its timestamp component.
ULIDs are useful when an application needs identifiers that can be generated independently while also benefiting from chronological ordering and a compact text representation.
Traditional auto-incrementing integers provide excellent ordering for database indexes, but they typically require coordination with a database sequence or another centralized ID generator. UUID v4 removes that coordination requirement by using random data, but random identifiers can have less favorable index locality in large, write-heavy database workloads.
ULID takes a different approach. Its first 48 bits contain a Unix timestamp in milliseconds, followed by 80 bits of additional data. This makes ULIDs time-ordered while retaining a very large identifier space for independently generated values.
Who Uses ULIDs?
ULIDs are useful for developers and systems that need independently generated identifiers together with chronological ordering.
Common use cases include:
- Backend engineers use ULIDs when identifiers need to be generated at the application layer rather than obtained from a database sequence.
- Distributed systems architects can use ULIDs when multiple services or nodes need to generate identifiers independently.
- Database administrators may consider ULIDs when random UUID v4 values create undesirable index locality in large write-heavy workloads.
- Event and logging systems can benefit from identifiers that contain creation-time information and sort chronologically.
- Test engineers can use ULIDs to generate unique test data while retaining useful time information for debugging.
ULIDs are not limited to large-scale systems. They can also be useful in ordinary applications when a compact, sortable identifier is convenient.
How Is a ULID Used?
A typical ULID is generated by the application before a record is sent to the persistence layer.
The basic workflow is:
- Generation: The implementation obtains the current Unix timestamp in milliseconds and generates the remaining identifier data according to the ULID specification.
- Encoding: The 128-bit value is represented using Crockford Base32, producing a canonical 26-character string.
- Transmission: The string can be used directly in JSON, REST APIs, GraphQL requests, URLs, logs, and other text-based protocols.
- Storage: The application can store the canonical 26-character string or use a compact 16-byte binary representation when storage and index size are important.
The canonical ULID representation uses the Crockford Base32 alphabet. It avoids several characters that are easily confused in human-readable strings, including I, L, O, and U.
A ULID is 128 bits regardless of whether it is stored as text or binary. The 26-character representation is an encoding of those 128 bits, not a larger identifier.
Is ULID Suitable for Databases?
Yes. ULIDs can be a good database key when an application needs independently generated identifiers and benefits from chronological ordering.
The primary advantage over UUID v4 is index locality.
Random UUID v4 values can cause inserts to be distributed throughout a B-tree index. In sufficiently large and write-heavy workloads, this can increase page splits, reduce cache locality, and increase index maintenance.
ULIDs place the timestamp at the beginning of the identifier. As a result, values generated around the same time are generally close together when sorted. This can provide better locality than randomly distributed identifiers.
However, ULIDs should not be treated as equivalent to an auto-incrementing database sequence. Multiple ULIDs can be generated during the same millisecond, multiple machines can generate identifiers concurrently, and system clocks can differ.
The actual database performance benefit depends on the database engine, index structure, storage representation, workload, and ULID generation strategy. For large systems, benchmarking with representative data is still the best way to evaluate the difference.
Storing ULIDs in a Database
ULIDs are 128-bit identifiers, but a ULID is not a UUID. The fact that both are 128 bits does not mean that a ULID should automatically be stored in a database's native UUID type.
For example:
- PostgreSQL: A ULID can be stored as
char(26),varchar(26), binary data, or through a suitable extension or custom type. PostgreSQL's nativeuuidtype should not be treated as a generic 128-bit container for ULIDs. - MySQL / InnoDB: A 16-byte binary representation can reduce storage and index overhead compared with a 26-character string. The appropriate representation depends on the application's tooling and requirements.
- SQL Server: A character representation or compact binary representation can be used.
uniqueidentifieris a UUID type and should not be assumed to be a native ULID type. - Oracle: Binary storage such as
RAW(16)can provide compact storage when appropriate. - SQLite: ULIDs can be stored as text or binary data. The best choice depends on the application's query patterns and tooling.
- Managed databases: Amazon RDS, Azure SQL, Google Cloud SQL, and similar services inherit the relevant storage and indexing behavior of their underlying database engines.
Storing a ULID as text is not automatically wrong. The 26-character representation is convenient for debugging, APIs, administration, and interoperability. A binary representation becomes more attractive when storage and index size are significant.
When Was ULID Created, and Where Is It Defined?
ULID was created as an independent identifier specification rather than as a version of the UUID standard.
The specification defines a 128-bit identifier consisting of a 48-bit Unix timestamp and 80 bits of randomness. Its canonical textual representation uses Crockford Base32 and is 26 characters long.
ULID became popular because it combined several useful properties in one format:
- distributed generation
- a large identifier space
- chronological ordering
- compact text representation
- URL-friendly characters
- no dependency on a central ID allocator
ULID is not an IETF RFC-defined UUID version. UUID v7 provides a standardized UUID-family alternative with many similar characteristics and is defined by RFC 9562.
Why Was ULID Created?
One of the main goals of ULID was to provide an identifier that could be generated independently while still being sortable by creation time.
An auto-incrementing integer is naturally ordered, but generating it usually requires coordination with a database or another central sequence.
UUID v4 solves the coordination problem by providing a large random identifier space, but its values have no chronological ordering.
ULID combines the two ideas. The timestamp is placed at the beginning of the identifier, while the remaining 80 bits provide a large space for random data.
This makes ULID useful when applications need identifiers that can be generated across multiple processes or machines without giving up useful chronological ordering.
Exactly How Many Bits Are Random?
A ULID consists of exactly 128 bits:
- 48 bits are used for the Unix timestamp in milliseconds.
- 80 bits are allocated to the random component.
The 48-bit timestamp provides a large range of representable millisecond timestamps, while the 80-bit random component provides a very large space for independently generated identifiers.
With 80 random bits, there are:
2^80
possible random values for a given millisecond.
That is approximately:
1.21 × 10^24
possible random values per millisecond.
The probability of an accidental collision is therefore extremely small for normal application workloads when the random component is generated correctly.
However, "guaranteed unique" would be incorrect. A collision is theoretically possible, and poor random-number generation, faulty implementations, or duplicated generator state can create risks far larger than the mathematical collision probability.
What Is Monotonic ULID Generation?
ULID's timestamp provides chronological ordering, but multiple identifiers can have the same timestamp because the timestamp has millisecond precision.
A ULID implementation can provide monotonic generation for identifiers created within the same millisecond. In a common monotonic strategy, the random portion is incremented when another ULID is generated during the same millisecond.
This can produce a sequence of ULIDs that continues increasing within the same timestamp.
Monotonic generation is an implementation feature rather than a guarantee that every ULID generated across every machine will be globally sequential.
This distinction matters in distributed systems. Two servers can generate ULIDs concurrently, and their clocks may not be perfectly synchronized.
Therefore, ULID should be described as time-ordered and potentially monotonic within an implementation, rather than as a globally sequential identifier.
Is ULID Sortable?
Yes. Sortability is one of ULID's defining characteristics.
The timestamp is encoded into the most significant portion of the identifier. Therefore, canonical ULID strings sort chronologically when compared using an appropriate lexicographical ordering.
For example, a ULID generated at an earlier timestamp will generally sort before one generated at a later timestamp.
However, sortable does not mean that every ULID is a perfect record of database insertion order.
Multiple identifiers can share the same millisecond timestamp, different systems can generate identifiers concurrently, and clock adjustments can affect ordering between independent machines.
For applications that require exact event ordering, an explicit sequence number, database ordering column, or other ordering mechanism may still be necessary.
Can ULIDs Be Used for Range Queries?
Yes. The timestamp-first layout makes time-based ranges possible using the identifier itself.
For example, an application can construct lower and upper ULID boundaries corresponding to a time interval and query identifiers between those values.
This can be useful for logs, events, and other records where the identifier's timestamp is meaningful.
However, a ULID does not eliminate the need for a created_at column in every application. A separate timestamp is often preferable when the application needs the exact creation time for display, reporting, filtering, auditing, or business logic.
The identifier and the timestamp serve different purposes even when the identifier contains timestamp information.
Is ULID Suitable for URLs?
Yes. ULIDs are particularly convenient for URLs because their canonical representation is only 26 characters long and uses a restricted Base32 alphabet.
For example:
https://example.com/orders/01JQ8Z7W4J4Q3J5Y8N6D7R2K1P
ULIDs offer several useful properties for URLs:
- Compact: 26 characters is shorter than the 36-character canonical UUID representation.
- URL-friendly: The canonical alphabet does not require special URL encoding.
- Human-friendly: Crockford Base32 avoids several visually ambiguous characters.
- Sortable: The identifier can be ordered chronologically.
ULIDs also make simple numeric enumeration impractical. However, this should not be confused with security.
A ULID is an identifier, not an authorization mechanism. Applications must still enforce authentication and authorization independently.
Does a ULID Reveal the Creation Time?
Yes.
The first 48 bits of a ULID contain the Unix timestamp in milliseconds. Someone who can decode a ULID can therefore determine approximately when it was generated.
This can be useful when debugging systems, analyzing logs, or sorting records.
It can also be undesirable when an application does not want resource identifiers to expose timing information.
For example, an application might not want a public URL to reveal approximately when an account, order, document, or transaction was created.
UUID v4 does not contain a timestamp and may therefore be preferable when hiding creation-time information is important.
Is ULID a Security Token?
No.
ULIDs should not be used as a substitute for dedicated security credentials.
Authentication tokens, session credentials, password-reset tokens, API secrets, and other security-sensitive values should use purpose-built cryptographic token mechanisms.
A ULID can be difficult to guess when generated correctly, but its timestamp is deliberately exposed and its purpose is identification rather than authentication.
Similarly, making a resource identifier difficult to guess does not replace server-side authorization.
When Is UUID v4 Still Appropriate?
The existence of ULID and UUID v7 does not make UUID v4 obsolete.
UUID v4 remains useful when an application wants a randomly generated identifier without an embedded timestamp.
Good use cases include:
- Public identifiers: When revealing approximate creation time is undesirable.
- Distributed generation: When independently generated random identifiers are needed.
- Existing UUID-based systems: When changing identifier formats provides little practical benefit.
- Moderate database workloads: When UUID v4's index-locality characteristics are not a significant performance concern.
- Random identifiers: When the application specifically benefits from a value that does not encode chronological information.
UUID v4 is worth evaluating carefully for very large write-heavy clustered indexes, but it is not automatically a database anti-pattern.
If index locality becomes a measurable problem, alternatives include UUID v7, ULID, sequential database keys, or other application-specific identifier strategies.
ULID vs UUID v7
This is one of the most useful comparisons for developers choosing a modern identifier format.
Both ULID and UUID v7 are 128-bit, time-oriented identifiers. Both can be generated independently and both provide chronological sorting.
The main differences are standardization, representation, and ecosystem compatibility.
UUID v7:
- Defined by RFC 9562
- 128 bits
- 48-bit Unix timestamp
- 74 additional bits available for random data, counters, additional timestamp precision, or combinations
- Canonical UUID representation is 36 characters
- Fits naturally into systems already built around UUIDs
- Part of the standardized UUID family
ULID:
- Defined by the ULID specification
- 128 bits
- 48-bit Unix timestamp
- 80-bit random component
- Canonical representation is 26 Crockford Base32 characters
- Compact and convenient in URLs and human-readable text
- Not an IETF-defined UUID version
If interoperability with UUID-based systems and RFC standardization are important, UUID v7 has a strong advantage.
If a compact, sortable, human-friendly textual representation is more important, ULID can be attractive.
Neither format is universally better.
ULID vs UUID v4
The fundamental difference is ordering.
UUID v4 uses 122 random bits and contains no creation-time component.
ULID uses a 48-bit timestamp and 80 bits of randomness.
That means UUID v4 provides a larger random space, while ULID provides chronological information and a sortable representation.
For database workloads where random primary keys create measurable index-locality problems, ULID is worth evaluating against UUID v4.
For applications where random identifiers without embedded timestamps are preferable, UUID v4 remains a strong choice.
Comparison to Other Identifiers
Identifier formats involve different tradeoffs between randomness, ordering, representation, standardization, and interoperability.
- UUID v1: Time-based UUID that includes timestamp and node-related information in its traditional layout. It can expose information that some applications may not want in a public identifier.
- UUID v3 and v5: Deterministic, name-based UUIDs. The same namespace and name produce the same identifier. They are useful for stable identifiers rather than randomly generated database keys.
- UUID v6: A time-ordered UUID based on the UUID v1 information layout, rearranged to make the timestamp more suitable for sorting.
- UUID v7: A modern time-ordered UUID using a Unix timestamp and additional generation data. It provides many of the same benefits as ULID while remaining part of the UUID family.
- Nano ID: A configurable random identifier generator. It can produce shorter identifiers than UUIDs, but random Nano IDs have the same general index-locality consideration as other randomly distributed identifiers when used as database keys.
Comparison Table
| Identifier | Bit Length | Random Bits | Time Component | Sortable | Text Length | Encoding |
|---|---|---|---|---|---|---|
| UUID v1 | 128 | Varies | Yes | Time-ordered* | 36 | Hex |
| UUID v4 | 128 | 122 | No | No | 36 | Hex |
| UUID v6 | 128 | Varies | Yes | Yes | 36 | Hex |
| UUID v7 | 128 | Up to 74** | Yes | Yes | 36 | Hex |
| ULID | 128 | 80 | Yes | Yes | 26 | Crockford Base32 |
| Nano ID | Configurable | Configurable | No | No | Configurable | Configurable |
- UUID v1 contains timestamp information, but its canonical representation was not designed primarily for lexical sorting. UUID v6 was introduced partly to provide a more naturally sortable representation.
** UUID v7 provides 74 bits in the rand_a and rand_b fields. Depending on the implementation, those bits may contain random data, counters, additional timestamp precision, or combinations of these.
Final Engineering Takeaway
ULID is a practical identifier format for applications that need independently generated IDs together with chronological ordering and a compact textual representation.
Its 48-bit timestamp and 80-bit random component provide a large identifier space, while its 26-character Base32 representation is particularly convenient for URLs, APIs, logs, and developer tooling.
ULIDs are also worth evaluating for database primary keys when random UUID v4 values create measurable index-locality problems. Their time-oriented layout can improve locality, although the actual benefit depends on the database engine, storage representation, workload, and generation strategy.
UUID v7 provides many of the same characteristics while remaining part of the standardized UUID family. UUID v4 remains appropriate when random identifiers without embedded time information are preferred.
There is no universally correct identifier format. The right choice depends on the application's database workload, distributed-generation requirements, storage constraints, interoperability needs, URL requirements, ordering requirements, and whether exposing approximate creation time is acceptable.
What is the ULID Text Layout?
What is the ULID Binary Bit Layout?
0100b
10b
Tools, UUID Versions Guide, dozens of articles in UUIDs In Databases & Engineering Blog.