What is a ULID?
A ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier encoded as a 26-character string using Crockford’s Base32 alphabet. ULIDs were designed to be a drop-in alternative to UUIDs that are inherently sortable by creation time, making them ideal for distributed systems, event sourcing, and database primary keys.
Unlike UUID v4 (which is entirely random), a ULID embeds a 48-bit millisecond-precision timestamp followed by 80 bits of cryptographically secure randomness. This means ULIDs generated later always sort after earlier ones — a property called lexicographic sortability.
ULID Structure
Every ULID is exactly 26 characters long and consists of two parts:
| Component | Size | Characters | Description |
|---|---|---|---|
| Timestamp | 48 bits | 10 (positions 1–10) | Unix time in milliseconds. Supports dates until the year 10889. |
| Randomness | 80 bits | 16 (positions 11–26) | Cryptographically random. Up to 1.21 × 1024 unique ULIDs per millisecond. |
The Crockford Base32 encoding uses the characters 0123456789ABCDEFGHJKMNPQRSTVWXYZ, which deliberately excludes I, L, O, and U to avoid ambiguity with digits and offensive words.
Key Features
- Lexicographically sortable— ULIDs sort correctly as plain strings, making them perfect for database indexes and ordered logs.
- 128-bit compatible— Same bit-width as UUID, so you can store ULIDs in existing UUID columns with no schema changes.
- URL-safe & compact— 26 characters with no hyphens or special characters, compared to 36 for a standard UUID.
- Case insensitive— Uppercase and lowercase are interchangeable; the canonical form is uppercase.
- Monotonically orderable— Implementations can optionally increment the random component within the same millisecond to guarantee strict monotonic ordering.
- No special characters— Safe for use in URLs, filenames, CSS selectors, and JSON keys without escaping.
ULID vs UUID v7
Both ULID and UUID v7 embed a millisecond timestamp for time-based sorting. The key differences are:
| Feature | ULID | UUID v7 |
|---|---|---|
| Encoding | Crockford Base32 (26 chars) | Hexadecimal (36 chars with hyphens) |
| Standard | Community specification | IETF RFC 9562 |
| Sortability | Lexicographic (string comparison) | Lexicographic (string comparison) |
| Size on wire | 26 characters | 36 characters |
| Ecosystem | Broad library support | Native UUID tooling, RFC-backed |
Choose UUID v7 when RFC compliance or seamless interoperability with existing UUID infrastructure is critical. Choose ULIDwhen you want shorter, URL-safe strings and don’t need to conform to the UUID format.
ULID vs UUID v4
UUID v4 is entirely random with no inherent ordering. While this is fine for many use cases, it causes B-tree index fragmentationin databases because inserts happen at random positions. ULIDs, being time-ordered, insert at the end of the index — dramatically improving write performance in high-throughput systems (PostgreSQL, MySQL, DynamoDB, etc.).
ULIDs also let you extract the creation timestamp directly from the identifier without a separate created_at column.
ULID vs Nanoid
Nanoid generates compact, random string IDs with a configurable alphabet and length. However, Nanoids have no time component and are not lexicographically sortable. If you need ordering by creation time, ULID is the better choice. If you need the shortest possible random ID, Nanoid wins.
When to Use ULID
- Database primary keys— Especially in write-heavy workloads where index locality matters (PostgreSQL, MySQL, CockroachDB, DynamoDB).
- Event sourcing & logging— Events naturally sort by time without an additional timestamp field.
- Distributed systems— Generate unique, collision-resistant IDs across nodes with no coordination.
- URL-safe identifiers— 26-character strings with no special characters work everywhere.
- Message queues & streams— Time-ordered IDs simplify consumer processing and debugging.
Code Examples
JavaScript / TypeScript
import { ulid } from 'ulid'
// Generate a new ULID
const id = ulid()
// e.g. "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// Monotonic factory — strictly increasing within the same ms
import { monotonicFactory } from 'ulid'
const monotonic = monotonicFactory()
const id1 = monotonic()
const id2 = monotonic() // guaranteed id2 > id1
// Extract timestamp
import { decodeTime } from 'ulid'
const ts = decodeTime('01ARZ3NDEKTSV4RRFFQ69G5FAV')
// ts = 1469918176385 (Unix ms)Python
import ulid
# Generate a new ULID
new_id = ulid.new()
print(new_id.str) # "01ARZ3NDEKTSV4RRFFQ69G5FAV"
# Extract timestamp
print(new_id.timestamp().datetime)
# From an existing ULID string
parsed = ulid.from_str("01ARZ3NDEKTSV4RRFFQ69G5FAV")
print(parsed.timestamp().int) # Unix msGo
import "github.com/oklog/ulid/v2"
// Generate a new ULID
id, err := ulid.New(ulid.Timestamp(time.Now()), entropy)
// Parse and extract time
parsed, _ := ulid.Parse("01ARZ3NDEKTSV4RRFFQ69G5FAV")
fmt.Println(ulid.Time(parsed.Time()))