2026-10-15 · 6 min read · Rahul Chitturi
- ulid
- identifiers
- databases
A ULID is 26 Crockford base32 characters. The first 10 are a 48-bit Unix timestamp in milliseconds. The last 16 are 80 bits of randomness. Sorted as canonical text, or as the 16 bytes with that timestamp in the high bits, newer ids tend to land after older ones.
The canonical alphabet is 0123456789ABCDEFGHJKMNPQRSTVWXYZ. It never uses I, L, O, or U. A generator that emits those letters is not writing a canonical ULID, and a decoder that maps them to 1 or 0 will disagree with one that rejects them.
Decode the prefix before you sort the column
Take the first 10 characters, uppercase them, and decode base32. The integer is milliseconds since 1970-01-01. The example prefix 01ARZ3NDEK is 1469922850259, which is 2016-07-30T23:54:10.259Z. If that instant is hours away from the row's created_at, the id was copied from another environment or the clock that minted it was wrong.
Divide by 1000 before you paste the value into a converter that expects seconds. Pasting the raw milliseconds produces a date centuries away and looks like a parser bug.
- Reject I, L, O, and U before you decode.
- Keep the original case in the ticket.
- Compare the decoded instant with created_at.
- Do not treat the prefix as a secret. The time is visible.
Do not expect order inside one millisecond
A non-monotonic generator draws fresh random bits for every id. Two ids from the same millisecond are ordered by those bits, not by which call returned first. A monotonic generator increments the random part when the millisecond does not change, so those ids do sort in creation order.
Mixed case breaks text sort even when the timestamps are in order, because ASCII uppercase and lowercase are different bytes. Normalize to uppercase before ORDER BY on a text column. A ULID stored as UUID bytes is a different string from the 26-character form. Diff those representations before you change the index.
Keep a version 4 fixture beside the ULID
A version 4 UUID does not encode a time. If a table used to store random UUIDs and a new writer stores ULIDs, an ORDER BY on the text column changes meaning. The ids are not the same format with a different name.
Flashman's UUID tool generates version 4 fixtures. It does not mint or decode ULIDs. Use the case converter to uppercase the 26 characters, the number-base converter on the decoded millisecond count, and the timestamp tool on the seconds. The number-base converter takes a decimal integer and shows binary, octal, and hex.