uuid.lol

How to Choose an ID Format

Pick an ID format by what it has to do, not by what it's called. If it lands in a database index, use UUID v7. If it shows up in a URL a person reads or types, use a Nano ID sized to your risk. If the same input must always produce the same ID, use UUID v5. Almost every other format on this site is a variation on one of those three.

Which ID format should I use?

It comes down to where the ID lives: a database index wants UUID v7, a URL wants Nano ID, a value that must reproduce itself wants UUID v5. A primary key or a foreign key benefits from sorting the same way the table does, so generate a UUID v7 and let the first 48 bits carry the timestamp. It's a real UUID: every database and every language already has a type for it.

A public identifier that shows up in a URL doesn't need any of that. Use a Nano ID and pick a length by the size of your ID space, not by habit. 21 characters is the library default, and it carries more entropy than a UUID v4 does in 36.

If the same input has to always produce the same ID, turning an email address into a stable ID for the same user however many times they sign in, use UUID v5. It hashes a namespace and a name with SHA-1 and never touches a random number generator.

What four questions decide the format?

Where it lives, who sees it, whether it must be reproducible, and whether hiding its creation time matters: those four questions, answered in order, decide almost every case.

  1. Does the ID live somewhere you query by range or insert into in order, like a database index? If yes, you want a sortable format.
  2. Will a person see it, in a URL, a support ticket, or a UI? If yes, shorter and case-insensitive is a real courtesy.
  3. Does the same input have to produce the same output, forever, with no state stored anywhere? If yes, only a deterministic format does this job; nothing random can.
  4. Would it hurt you if someone worked out roughly when the ID was made, or how many you've issued? If yes, rule out every sortable format below. None of them hide that.

Which format fits which job?

The table below is the four questions above, turned into a lookup: match your job to a row and use what it says.

Job Use Why
Primary key or index in a database UUID v7 Sorts with insertion order; 74 bits of randomness is plenty for a key nothing outside your database ever sees
Public ID in a URL a person reads or types Nano ID Tunable length, no timestamp to leak, URL-safe by default
Same input must always yield the same ID UUID v5 Deterministic SHA-1 hash of a namespace and a name, no RNG involved
Shown to people who shouldn't guess volume or timing UUID v4 122 bits from a CSPRNG, no timestamp field at all
A MongoDB document's _id ObjectID Already the collection default; fighting it buys you nothing
One system assigning every worker a number, with no shared RNG Snowflake Coordinated, not random: a worker ID plus a per-worker sequence

How do all ten formats compare on cost and carry?

Line every format up by bits and the tradeoff is visible directly. KSUID and ObjectID spend 32 bits of their width on a clock, UUID v7, ULID and TypeID spend 48, and Snowflake spends 41 on a custom epoch. UUID v4, UUID v5, Nano ID and Cuid2 spend none, because none of them carries a clock at all.

Every format on this site, by what it costs and what it carries.
FormatBitsEntropyTimestampSorts by timeLength
UUID v4128122 bitsNoneNo36 characters
UUID v712874 bitsMilliseconds since 1970-01-01Yes36 characters
UUID v5128DeterministicNoneNo36 characters
ULID12880 bitsMilliseconds since 1970-01-01Yes26 characters
Nano ID126126 bitsNoneNo21 characters by default, tunable
Cuid2123Hashed, unstatedNoneNo24 characters by default, tunable
KSUID160128 bitsSeconds since 2014-05-13Yes27 characters
TypeID12874 bitsMilliseconds since 1970-01-01Yes26 characters plus the prefix
ObjectID9640 bits + counterSeconds since 1970-01-01Roughly24 characters
Snowflake63Coordinated, not randomMilliseconds since a custom epochYesup to 19 digits

Should the ID sort by creation time or stay random?

Sort by time when you'll range-scan or paginate by it; stay random when hiding order matters more than locality. A UUID v7 primary key appends to the end of a B-tree instead of scattering writes across it, and a ULID or a KSUID does the same job for an application ID a person might paste into a support ticket.

The cost is that the timestamp isn't hidden. Anyone holding a UUID v7, a ULID, a KSUID, a TypeID or an ObjectID can read approximately when it was minted, and comparing two of them tells you which came first. RFC 9562 defines v7's clock as milliseconds since 1970-01-01, sitting in plain hex in the first 48 bits.

UUID v4, UUID v5 and Nano ID carry no clock at all. That's the whole reason to reach for one of them when leaking order would be a problem.

How long does an ID need to be?

Long enough that your collision odds are lower than every other failure mode in your system, which for most applications means at least 120 bits of real randomness. Length and entropy are not the same measurement: a ULID is 26 characters against a UUID's 36 and holds the identical 128 bits, because base32 packs more bits per character than hex with hyphens does. A Nano ID's 21-character default carries 126 bits, more randomness than a UUID v4's 122, in 15 fewer characters.

What matters is how much randomness survives collisions at your volume. Using the standard birthday approximation, n₅₀% ≈ 1.1774 × √(2^bits), a UUID v4's 122 bits needs about 2.7 × 10¹⁸ values minted before there's a 50% chance of one collision anywhere in the set. At a sustained one billion generated per second, that's roughly 86 years of continuous generation.

A Nano ID's 126 bits stretches that same math to about 344 years at the same rate, which is why 21 characters is the library default and not a compromise.

Timestamped formats change the question, and this is the caveat that keeps v7 honest. UUID v7's 74 random bits only have to avoid a collision with IDs stamped in the same millisecond, not with every ID you'll ever mint, because two different milliseconds can never collide regardless of what the random half holds. Run the same formula on one bucket: even at an absurd one million v7 values minted inside a single millisecond, a sustained billion inserts a second, the chance two of them share both the timestamp and the random bits works out to about 1 in 38 billion.

Where is each format the wrong choice?

Every format on this site is wrong somewhere. Here's where.

Common questions

Is UUID v4 still a fine default?

Yes. 122 bits from a CSPRNG isn't guessable, and every language and database already understands the type. Reach for v7 once you want sort order, and not before; don't add a timestamp field you have no use for.

Do I need a library to generate any of these?

Not for UUID v4: crypto.randomUUID() is built into every modern browser and every recent version of Node. Everything else here, v7, v5, ULID, Nano ID, needs a small library, or our generators, which run the same algorithms client-side and send nothing to a server.

How do I generate a UUID v7 in Postgres?

Postgres 18 added it natively, no extension required:

select uuidv7();

On Postgres 17 or earlier, you need a third-party extension or a v7 value generated in your application and passed in as a parameter. Check the PostgreSQL UUID documentation for your version before assuming the function exists.