ID Decoder: UUID, ULID, ObjectId and Snowflake
After reading this you can look at any opaque identifier, name its format and version, and pull out the creation timestamp it hides, down to the millisecond when the format allows it.
What these IDs are and where the time hides
Most systems that create records need a unique identifier for each one. Several popular formats pack a timestamp into that identifier so you can learn when a record was born without a separate created_at column. The trick is knowing where each format keeps the clock.
Take this ULID: 01ARZ3NDEKTSV4RRFFQ69G5FAV. The first 10 characters, 01ARZ3NDEK, are a 48-bit millisecond timestamp. Decoded, that is 1469918176385 milliseconds since the Unix epoch, which is 2016-07-30T23:54:10.385Z. The remaining 16 characters are random. The decoder reads only those first 10 characters to recover the date.
The four formats this tool understands are UUID (versions 1, 4 and 7), ULID, MongoDB ObjectId, and Snowflake (as used by Twitter and Discord). One of them, UUID v4, carries no time at all. The rest do.
When to decode an ID, and when not to
Decoding is useful for debugging and forensics. If a support ticket references order 018f2e... and you need to know roughly when it was created, a UUID v7 gives you the answer straight from the string. If you are correlating events across logs and one system only stored the ID, the embedded timestamp can stand in for a missing date field.
Do not treat the timestamp as authoritative. A client can forge a UUID v7 with any timestamp it likes, since the format is public and the value is not signed. Use the embedded time as a hint, not as evidence. For billing, auditing or anything a user could benefit from lying about, keep a server-set timestamp in its own column.
An ID is not a secret and its timestamp is not tamper proof. A ULID or Snowflake reveals your creation rate to anyone who collects a few of them: two IDs made one second apart differ by a predictable amount in their time bits. If that leaks business volume you would rather hide, do not expose sequential time-based IDs in public URLs.
The math of a packed timestamp
Every time-bearing format here stores the clock as an integer count of time units since a fixed starting point called the epoch. To read it you extract the right bits, then convert with one formula.
Here raw is the identifier as one big integer, s is how many low bits to shift away (the random or sequence bits), u is the unit in milliseconds, and e is the epoch offset in milliseconds. The shifted value is the timestamp in the format's own units, and multiplying by u plus e lands you on standard Unix milliseconds.
The four formats plug different constants into that formula:
| Format | Time bits | Shift s | Unit u (ms) | Epoch e | Resolution |
|---|---|---|---|---|---|
| UUID v7 | high 48 | 80 | 1 | 1970-01-01 | 1 ms |
| ULID | high 48 | 80 | 1 | 1970-01-01 | 1 ms |
| ObjectId | high 32 | 64 | 1000 | 1970-01-01 | 1 s |
| Snowflake (Twitter) | high 41 | 22 | 1 | 2010-11-04 | 1 ms |
| Snowflake (Discord) | high 41 | 22 | 1 | 2015-01-01 | 1 ms |
UUID v1 is the odd one. It stores time in 100-nanosecond units counted from 1582-10-15 (the Gregorian calendar reform), and the 60-bit count is split across three fields that must be reassembled before the formula applies. The tool handles that stitching for you.
A worked example with the demo data
Decoding the ULID 01ARZ3NDEKTSV4RRFFQ69G5FAV
ULID uses Crockford's base32, which maps 32 symbols to 5 bits each. The first 10 symbols hold the 48-bit timestamp (10 × 5 = 50 bits, and the top 2 are always 0).
- Read the time symbols:
0 1 A R Z 3 N D E K. - Map each to its value:
0=0, 1=1, A=10, R=24, Z=31, 3=3, N=22, D=13, E=14, K=20. - Combine base32, most significant first: the running total after each symbol is 0, 1, 42, 1368, 43799, 1401571, 44850294, 1435209421, 45926701486, then 1469654447585... continue to the last symbol to reach
1469918176385. - That integer is already Unix milliseconds because s=80 is applied at the byte level, u=1 and e=0.
- Convert:
1469918176385 / 1000 = 1469918176.385seconds, which is2016-07-30T23:54:10.385Z.
Paste the same string into the tool and press the demo button. It reports the format as ULID and the time as 2016-07-30 23:54:10.385 UTC, matching the hand calculation.
How the epoch changes the answer
Snowflakes are where readers get burned, because the raw integer means nothing until you subtract the right epoch. The same 64-bit Snowflake decoded against Twitter's epoch (1288834974657 ms) and Discord's epoch (1420070400000 ms) produces dates about 4.2 years apart, since the epochs differ by 131235425343 ms.
The tool tries both and shows which yields a plausible modern date. The chart below shows how the decoded year shifts as you slide the assumed epoch for a fixed Snowflake whose 41 time bits equal 400000000000 ms.
Reading and interpreting the result
The decoder reports three things: the detected format, the version or variant, and the timestamp if one exists. Match them against what you expected.
- Format
- Which family the string belongs to. A 36-character string with dashes is a UUID; a 26-character Crockford base32 string is a ULID; a 24-character hex string is an ObjectId; a bare 17 to 19 digit number is likely a Snowflake.
- Version
- For UUIDs, the single hex digit at position 13 (the start of the third group).
4means random,1means time-and-MAC,7means time-ordered. - Variant
- The two or three high bits of the digit at position 17. RFC 4122 defines the common variant as binary
10x, which shows as a hex digit of8,9,aorb. - Timestamp
- The extracted creation time in UTC. Absent for UUID v4 and for any string with no time bits.
If the decoded date lands in the future or before the format existed (a ULID cannot predate 1970, a Twitter Snowflake cannot predate 2010), treat the detection as suspect. You may have pasted a truncated string or guessed the wrong format.
Common mistakes
Reading a v4 UUID for time is the first one. Version 4 is 122 random bits with no clock, so any "timestamp" you extract from it is noise. Check the version digit before you trust a date.
Confusing seconds and milliseconds is the second. An ObjectId stores 32 bits of seconds, so its resolution is 1 second and its range runs out in 2106. UUID v7 and ULID store milliseconds. If you feed a bare number into a generic converter, decide the unit first with the Epoch Millis vs Seconds Detector.
The third is byte order. UUID v1 splits its 60-bit time across three fields in a deliberately awkward layout: the low 32 bits come first, then the mid 16, then the high 12 with the version nibble mixed in. Reading the fields left to right gives a wrong, wildly wrong date. Let the tool reassemble them.
When two candidate formats fit a string, prefer the one whose decoded date is plausible. A 24-hex-character value is almost always an ObjectId, but a 128-bit hex UUID with dashes stripped is also 32 hex characters, so length is your first check: 24 means ObjectId, 32 means UUID.
Related tools
Once you have a Unix timestamp in hand, the Unix Timestamp Converter turns it into a date in any timezone or ISO 8601 form. To create fresh identifiers rather than read them, use the UUID / ULID / Nano ID Generator, which makes time-ordered v7 and ULID values in bulk. If your work involves scheduling rather than IDs, the Cron Expression Parser and the Crontab Builder cover the writing and reading of cron syntax.
Frequently asked questions
Why does a UUID v4 show no timestamp?
Version 4 is defined as random. Of its 128 bits, 6 are fixed for version and variant and the other 122 are random. There is no clock to extract, so no date can be honest. Use v1, v7, ULID, ObjectId or Snowflake if you need embedded time.
How do I know if a number is a Snowflake or a plain Unix timestamp?
Size is the clue. A Unix timestamp in milliseconds is about 13 digits (currently near 1.7 x 10^12). A Snowflake is 18 to 19 digits because its time bits are shifted left by 22, multiplying the value by roughly 4.19 x 10^6. If the number is 17 digits or more, it is almost certainly a Snowflake.
Which Snowflake epoch should I use?
Match the service. Twitter and X use 2010-11-04T01:42:54.657Z. Discord uses 2015-01-01T00:00:00Z. The tool decodes with both and shows which gives a plausible date, but if you know the source, pick that one to avoid a 4.2-year error.
Can I trust the embedded time for auditing?
No. The value is not signed and a client can generate an ID with any timestamp. Use it as a debugging hint and keep an authoritative server-set timestamp for anything that matters.
Does anything I paste get uploaded?
No. The decoder runs entirely in your browser. The IDs you paste never leave your device, which matters because identifiers can appear in URLs and logs you would rather not share.