flashman
← All guides

Read the time prefix on a ULID

Decode a ULID time prefix from Crockford base32, compare monotonic and random generators, and sort canonical uppercase text instead of mixed-case copies.

2026-10-15 · 8 min read

  • ulid
  • identifiers
  • databases

A ULID packs a 48-bit millisecond timestamp and 80 random bits into 128 bits, then writes them as 26 Crockford base32 characters. The first 10 characters are the timestamp. The last 16 are the entropy. The layout is why a canonical ULID sorts by creation time at millisecond resolution.

That sort is a property of the encoding, not of every string that is 26 characters long. The alphabet, the case, and the generator's monotonic option all change what an ORDER BY means.

Decode ten characters to milliseconds

The Crockford alphabet is 0123456789ABCDEFGHJKMNPQRSTVWXYZ. Encoding is case-insensitive to read, and the canonical form is uppercase. I, L, O, and U are not in the alphabet. Some decoders accept I and L as 1 and O as 0. A strict decoder rejects them. Agree on one behavior before you compare ids from two services.

The prefix 01ARZ3NDEK decodes to 1469922850259 milliseconds, the instant 2016-07-30T23:54:10.259Z. Compare that instant with the row timestamp. A large gap means the id was imported, the minting clock stepped, or the column is not a ULID.

  • Uppercase the text before decoding.
  • Reject or map I, L, O, and U on purpose.
  • Keep the remainder after dividing by 1000.
  • Do not publish the prefix as if it were random.

Separate monotonic ids from random ids

Inside one millisecond, a random generator does not promise call order. The entropy bits are the sort key. A monotonic generator increments those bits when the clock does not advance, so ids created in that millisecond sort in creation order on that process.

Two processes can still emit ids in the same millisecond that interleave. A step backward in the clock can insert a new id before an older one. If the product needs a gapless sequence, use a database sequence. Use a ULID when you want a time-ordered identifier that also stands alone.

Sort one representation

Uppercase Crockford text sorts in time order. Mixed case does not, because lowercase letters sort after uppercase in byte order. A ULID stored in a UUID column is the same 128 bits in a different text form. The hyphenated hex string is not the 26-character string, and sorting one does not test the other.

Normalize case in the application if the column is text. If the column is uuid, sort the binary value and keep the Crockford form for APIs. Diff a sample of both forms when a migration changes the column type.

Check the prefix with ordinary converters

Flashman's UUID tool generates version 4 ids. Those fixtures are useful next to a ULID because they do not sort by time and they do not decode to a timestamp. They are not substitute ULIDs.

Uppercase the identifier with the case converter. Convert the decoded millisecond count from decimal to hex with the number-base converter to see the 48-bit width. Divide by 1000 and paste the seconds into the timestamp tool. The number-base converter does not decode base32, so the Crockford step stays in your decoder or in a short local script.

Try these tools