flashman
← All guides

Read a snowflake id without losing the sequence

Decode a 64-bit snowflake by naming its epoch and field widths, shift off the low 22 bits for the clock, and keep the decimal id as a string in JSON.

2026-10-16 · 8 min read

  • identifiers
  • json
  • time

Snowflake is a layout, not a standard with one epoch. The shared idea is a 64-bit integer: a millisecond timestamp in the high bits, then generator fields, then a sequence that increments inside the same millisecond. The timestamp is an offset from an epoch the system picked. Two vendors can both say snowflake and disagree by years.

Twitter's published layout is 41 bits of time since 1288834974657 (2010-11-04T01:42:54.657Z), then 5 bits of datacenter, 5 bits of worker, and 12 bits of sequence. The top bit stays 0. Discord's layout is 42 bits of time since 1420070400000 (2015-01-01T00:00:00.000Z), then 5 bits of worker, 5 bits of process, and 12 bits of increment. Both expose the time with a right shift of 22.

Decode one id on paper before you change code

Take the decimal string, not a floating-point parse. Shift right 22. Add the epoch you believe this system uses. The sum is a Unix millisecond instant. For the constructed Twitter-layout value 1724551110456668202 and epoch 1288834974657, the instant is 2023-11-14T22:13:20.000Z. The same shift with Discord's epoch reads 2028-01-11T20:30:25.343Z. If the row's created_at agrees with only one of those, you have found the epoch.

Then mask. The low 12 bits are the sequence: that example's sequence is 42, the next 5 bits are worker 7, and the next 5 are datacenter 3. Discord's example 175928847299117063 with the 2015 epoch is 2016-04-30T11:18:25.796Z, worker 1, process 0, increment 7. A layout with a different split will still produce a plausible date and nonsense generator ids. Check a second id from the same generator before you trust the field widths.

  • Record epoch, shift, and field widths in the same line of the runbook.
  • Reject a date before the product existed. That is the wrong epoch.
  • Compare the decoded instant with the row's stored timestamp.
  • Do not sort a mixed column of Twitter-layout and Discord-layout ids.

Keep the id a string across JSON

JavaScript numbers cannot represent every integer above 2^53 − 1. Parsing 1724551110456668202 as a number yields 1724551110456668160. The time bits still say 2023-11-14T22:13:20.000Z, and the sequence becomes 0. Anything that round-trips the id through JSON.parse and JSON.stringify without a reviver will store a different id that still looks timely.

APIs should emit the decimal as a JSON string. Clients that need the clock can convert the string with a big-integer type, shift, and add the epoch. Clients that only need to send the id back should send the original characters.

Do not confuse it with a UUID or a ULID

A version 7 UUID stores a Unix millisecond timestamp in its high bits and is 128 bits, with a version nibble. A ULID is 26 Crockford base32 characters and also uses a Unix millisecond prefix. A snowflake is a decimal integer and an offset from a custom epoch. Decoding one with the rules of another produces a date that is merely close enough to start an argument.

The sequence space is 12 bits, so one generator can mint 4096 ids in the same millisecond. Past that, a correct implementation waits for the next millisecond or fails. A duplicate primary key inside one millisecond is a generator bug or a reused worker id, not a reason to change the epoch.

Check the string, the seconds, and the small fields

Format the payload with Flashman's JSON formatter and see whether the id is quoted. Diff that text with what the database driver stored. Divide the decoded milliseconds by 1000 and paste the integer seconds into the timestamp converter: 1700000000 is 2023-11-14T22:13:20.000Z. Pasting 1700000000000 makes the converter treat milliseconds as seconds.

The UUID tool generates version 4 ids for fixtures. It does not mint snowflakes. The number-base tool uses ordinary JavaScript integers, so leave the full id out of it. After you have masked the worker or sequence, those small integers are safe to convert. The original decimal string remains the value you persist.

Try these tools