Quick answer
Use a UUID unless you have a clear reason not to. Pick UUID v7 for database keys and UUID v4 when the ID must reveal nothing. Pick a ULID only if you want a shorter, sortable string and your tools do not care about the UUID format. Pick Nano ID for short, random IDs that appear in URLs, such as share links.
At a glance
All three are made on your own machine, with no central counter, and are random enough that two systems will not clash in practice.
| UUID v4 | UUID v7 | ULID | Nano ID (default) | |
|---|---|---|---|---|
| Length as text | 36 | 36 | 26 | 21 |
| Characters | 0-9, a-f, - | 0-9, a-f, - | 0-9, A-Z (no I L O U) | A-Z, a-z, 0-9, _ - |
| Size in binary | 16 bytes | 16 bytes | 16 bytes | no binary form |
| Random bits | 122 | up to 74 | 80 | 126 |
| Creation time inside | No | Yes, to 1 ms | Yes, to 1 ms | No |
| Sorts by creation time | No | Yes | Yes | No |
| Defined by | RFC 9562 | RFC 9562 | Community spec | One library |
What each one is
UUID
A UUID is a 128-bit number written as 32 hex digits in five groups. The current standard, RFC 9562, defines several versions. Version 4 is almost all random. Version 7 puts a millisecond timestamp first and fills the rest with random bits, so new IDs sort after old ones.
- Timestamp, 48 bits
- Random, 74 bits
- Version, always 7
- Variant, 8, 9, a or b
The big advantage is support. PostgreSQL, SQL Server and MariaDB have a native UUID type, and nearly every language has a UUID library built in or one install away.
ULID
A ULID is also 128 bits: a 48-bit millisecond timestamp followed by 80 random bits. It is written in Crockford’s Base32, which skips the letters I, L, O and U to avoid misreading. That makes it 26 characters instead of 36.
01M5M1H1E0FCZ9YAJCDP0Y0PKK
The first 10 characters are the time. This ULID and the UUID v7 in the diagram above hold the same 128 bits, just written differently. A ULID is not a UUID, though. It has no version or variant bits, and most databases have no ULID type, so you usually store it as text or convert it to a UUID column.
Nano ID
Nano ID is a small JavaScript library, with ports in many other languages. By default it makes 21 characters from a 64-symbol, URL-safe alphabet, which gives 126 random bits. You can change the length and the alphabet.
import { nanoid } from 'nanoid';
nanoid(); // e.g. "niizb0DrS4RglwcKIiXBO"
There is no time inside and no fixed format. It is just a random string.
Sorting and database keys
Most databases store a primary key in a B-tree index, a structure kept in sorted order. When each new key is larger than the last, it goes at the end of the index and the pages being written stay in memory. When keys are random, each insert lands somewhere different, so the database touches more pages and has to split full ones more often. This matters more as a table grows.
UUID v7 and ULID both sort by time, so they behave well as primary keys. UUID v4 and Nano ID are random, so they do not. If you use them as keys on a large, busy table, expect a bigger and slower index than with sorted IDs.
Size matters too. A UUID or ULID fits in 16 bytes when stored as binary. A Nano ID has no binary form, so it is stored as text, which takes more space and is slower to compare.
Privacy and guessing
None of these IDs can be guessed in practice, as long as they come from a secure random source. The difference is what they reveal.
UUID v7 and ULID show the moment they were created, to the millisecond. If an ID appears in a URL, anyone can read when that account or order was made, and roughly how fast you create them. UUID v4 and Nano ID reveal nothing.
How to choose
- Is it a database key? Use UUID v7.
id uuid PRIMARY KEY DEFAULT uuidv7() - Must it reveal nothing, and is it not a big table key? Use UUID v4.
- Does it go in a URL and need to be short? Use Nano ID.
- Do you need a sorted ID as short text and control every system it touches? A ULID works.
When in doubt, a UUID is the safe default. Other teams, tools and databases already understand it.
Good to know
Short IDs mean fewer random bits
A Nano ID with 8 or 10 characters is much easier to collide with. See how likely a collision is before you shorten one.
A ULID can become a UUID
Since both are 128 bits, you can store a ULID in a UUID column. It will not have valid version bits, so some validators will reject it.

