What a ULID actually is#
A ULID is 128 bits, the same size as a UUID, but encoded and structured differently. The first 48 bits are a millisecond-precision Unix timestamp; the remaining 80 bits are random. The whole thing is rendered as 26 characters of Crockford's Base32 — a modified alphabet that deliberately excludes the letters I, L, O and U, so a handwritten or read-aloud ULID cannot be confused between a 1, an I and an L, or a 0 and an O.
How this compares to UUID v7, this site's other time-sortable ID#
UUID v7 and ULID solve the identical problem — a randomly-distributed identifier that also sorts by creation time, avoiding the database index fragmentation that plain UUID v4 causes as a primary key — using nearly the same structure: both encode a 48-bit millisecond timestamp followed by random bits. The practical difference is encoding and ecosystem: UUID v7 is a hex-formatted, hyphenated string that fits the standard uuid type in most databases and matches an official IETF standard (RFC 9562); ULID is a Base32 string, one character shorter, avoids visually ambiguous characters, and predates the UUID v7 standard by several years as the original popularizer of this idea. Neither is more "correct" — the deciding factor is usually which one a project's existing tooling or database column type already expects.
Why the randomness matters here too#
Just as with this site's UUID and password generators, the 80 random bits come from crypto.getRandomValues, the browser's cryptographically secure random source — not Math.random, which has a predictable internal state. Two ULIDs generated in the same millisecond still differ with overwhelming probability, since 80 bits of true randomness is an enormous space to collide within.