KSUID Decoder
All processing happens in your browser. No data is sent to any server.
About this tool
How a KSUID is put together
A KSUID is 160 bits, which is 20 bytes, printed as exactly 27 characters of Base62 using digits, uppercase, and lowercase letters. The first 4 bytes are a 32-bit unsigned count of seconds since the KSUID epoch, and the remaining 16 bytes are a cryptographically random payload. The epoch is 13 May 2014, chosen so that the 32-bit second counter does not exhaust until the year 2150 while leaving no wasted range on dates before the format existed; a plain Unix epoch would have burned 44 years of the available span on the past. Because the timestamp occupies the most significant bytes and Base62 encoding preserves the ordering of the underlying integer, comparing two KSUIDs as strings orders them by creation second. The 128-bit random payload is the largest of any common sortable ID format, which makes collisions within a single second effectively impossible even across a large fleet generating independently. The fixed 27-character length is a deliberate design choice: every KSUID is the same width, so they align in logs and fit predictably in fixed-width database columns, unlike a variable-length Base62 encoding of an arbitrary integer.
Second precision and what it costs
The defining trade in KSUID is spending timestamp precision to buy random payload. A KSUID timestamp resolves to one second, where ULID and UUID v7 resolve to one millisecond, and in exchange KSUID carries 128 random bits against ULID's 80 and UUID v7's 74. The consequence is that all KSUIDs generated within the same second share an identical timestamp prefix and are then ordered by their random tails, which is to say arbitrarily. For the workloads the format was designed for, storing and paging through events where the useful granularity is a second or coarser, that is invisible. It becomes a real problem when you need to reconstruct the order of operations inside a second, for example when replaying a sequence of writes to the same record, and in that case a millisecond-precision format is the right tool. The larger random payload does buy something concrete: with 128 bits of entropy per second you can generate at essentially any rate without collision risk and without the coordinated monotonic counter that millisecond formats need to stay safe under bursts, so a KSUID generator is stateless and trivially safe to run in parallel across as many processes as you like.
Frequently Asked Questions
KSUID vs UUID — which should I use?
They answer different questions, so the choice follows from what your system needs. UUID v4 wins on compatibility: every language, database, and framework has native support, the format is standardised in RFC 9562, and nothing about it will surprise a future maintainer. Its weakness is that the values are random, so using one as a primary key scatters inserts across a B-tree index and fragments it, which measurably degrades write throughput and inflates index size on large tables. KSUID fixes the ordering problem by putting a timestamp in the leading bytes, so inserts append rather than scatter, and it prints as 27 URL-safe Base62 characters instead of 36 characters with hyphens. Its weaknesses are that it is 160 bits rather than 128, so it does not fit a native UUID column and must be stored as a 20-byte binary or a 27-character string, its timestamp resolves only to the second, and support comes from libraries rather than from language standard libraries. If you want time-ordered keys and can accept a non-standard column type, KSUID is a good fit. If you want time-ordered keys and need to stay inside the UUID ecosystem, UUID v7 gives you most of the benefit with none of the compatibility cost.
KSUID vs ULID — what is the difference?
Both are sortable identifiers encoded as URL-safe text, and they differ in size, precision, and alphabet. KSUID is 160 bits printed as 27 Base62 characters, with a 32-bit second-precision timestamp offset from 13 May 2014 and a 128-bit random payload. ULID is 128 bits printed as 26 Crockford Base32 characters, with a 48-bit millisecond timestamp from the Unix epoch and an 80-bit random payload. The millisecond timestamp is ULID's main advantage, giving finer ordering and an optional monotonic mode that guarantees strict ordering within a millisecond. ULID's Crockford Base32 alphabet is also friendlier to humans, since it excludes the confusable characters I, L, O, and U and is case-insensitive, which matters if anyone will ever read an ID aloud or type it from a screenshot; KSUID's Base62 is case-sensitive and includes every confusable pair. KSUID's advantages are its much larger random payload, which removes any need for coordination between generators, and the fact that it fits neatly as 20 raw bytes. ULID additionally converts cleanly into a 128-bit UUID column, which KSUID cannot do.
Why does my KSUID decode to a date in 2014?
Because the timestamp has been read against the Unix epoch instead of the KSUID epoch. A KSUID stores seconds elapsed since 13 May 2014, so a decoder that adds the raw counter to 1 January 1970 lands roughly 44 years and 4 months early, which for recent IDs puts the result somewhere in the early 1980s, and a decoder that forgets the offset entirely reports a date very close to 13 May 2014 itself. Adding 1400000000 seconds, the KSUID epoch expressed in Unix time, corrects it. Two other causes are worth ruling out. If the string is not exactly 27 characters, it is not a KSUID: a 26-character string is a ULID and will decode to nonsense under KSUID rules. And if the value came from a JSON payload that passed through code treating IDs as numbers, the Base62 text may have been mangled; KSUIDs must be handled as strings throughout.
Standards & References
- segmentio/ksuid — Reference implementation and format description by Segment