Nano ID vs UUID: choosing an identifier format
A UUID v4 is 36 characters of hyphenated hexadecimal carrying 122 bits of randomness. A Nano ID is 21 characters from a 64 symbol alphabet carrying 126 bits. They solve the same problem, generating an identifier without coordinating with anything, and they differ mainly in encoding efficiency and in what surrounding systems expect. Choosing between them is less about safety than about where the identifier is going to appear.
You can generate either format, in bulk, with the UUID and token generator.
The comparison
| UUID v4 | Nano ID (default) | |
|---|---|---|
| Length | 36 chars | 21 chars |
| Entropy | 122 bits | 126 bits |
| Alphabet | Hex plus hyphens | A-Za-z0-9_- |
| URL safe | Yes | Yes |
| Standardized | RFC 9562 | No, a library convention |
| Native database type | Yes, 16 bytes | No, stored as text |
| Sortable by creation | No | No |
| Double click selects whole ID | No, hyphens break it | Yes |
Nano ID is shorter for the same security because it encodes more per character. Hexadecimal carries four bits per character; a 64 symbol alphabet carries six. UUID additionally spends four bits on the version and two on the variant, which is why 128 bits of format yields 122 bits of randomness.
Is a shorter ID more likely to collide?
At these sizes, no. Both are far past the point where collision is a practical concern: generating a billion IDs per second for a century leaves the probability of any collision negligible for either. The comparison people should actually make is not UUID against Nano ID, it is either of them against a shortened version.
Shortening is where the risk becomes real, because the birthday bound scales with the square root of the space. A useful rule:
| Length (64 symbol alphabet) | Bits | IDs before 1% collision chance |
|---|---|---|
| 21 | 126 | Effectively unbounded |
| 12 | 72 | ~10 billion |
| 10 | 60 | ~150 million |
| 8 | 48 | ~2.4 million |
| 6 | 36 | ~37 thousand |
Eight characters looks perfectly reasonable in a URL and starts colliding after a couple of million rows, which for a growing product is a year rather than never. If you shorten, compute the bound for your expected volume rather than choosing by appearance, and add a unique constraint so a collision is a retry rather than data corruption.
The other thing shortening breaks is unguessability. An identifier used in a URL without an authorization check, the classic "anyone with the link" pattern, is only as secure as it is hard to enumerate. At 48 bits, guessing is expensive but not absurd; at 126 bits it is impossible. Short IDs are fine as identifiers and unsuitable as capabilities.
Randomness quality matters more than format
Both formats depend on a cryptographically secure random source. Math.random() is not one: it is a fast pseudorandom generator, seeded from a small amount of state, and its output is predictable from previous outputs. An ID built on it can be enumerated even at full length.
The correct sources are crypto.getRandomValues() in the browser, crypto.randomUUID() for UUIDs specifically, and crypto.randomBytes() in Node. Nano ID uses the platform CSPRNG by default; the risk is in hand rolled "short ID" helpers found in most codebases, which almost always use Math.random().toString(36).
crypto.randomUUID() // 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
crypto.getRandomValues(new Uint8Array(16))
The same distinction governs anything security bearing: session tokens, password reset links, API keys. If an attacker guessing the value would be a problem, the value must come from a CSPRNG. The token generator uses the browser's crypto API and never transmits what it produces.
Where each one fits
Use UUID when the identifier crosses a boundary. Databases have a native 16 byte UUID type, which is half the storage of a 21 character string and indexes faster. Postgres, SQL Server, and most ORMs understand it natively. Standard libraries in every language parse and generate it. Logging pipelines, tracing systems, and partner APIs recognise the shape. If the ID appears in a schema other people implement against, the standard is worth more than nine saved characters.
Use Nano ID when the identifier is user facing. Shorter URLs, no hyphens breaking double click selection, and no visual resemblance to the internal database key that a UUID has. It reads as an application level identifier rather than a leaked primary key.
Use UUID v7 when rows are inserted in order. This is the more consequential choice than either of the above. A v4 UUID is fully random, so successive inserts land in random positions of a B-tree index, fragmenting pages and destroying insert locality on large tables. UUID v7 puts a millisecond timestamp in the leading bits, so IDs sort by creation time and inserts append. The full argument, including why this matters more than storage size, is in UUID v4 vs v7 for database keys.
Nano ID has no time ordered variant, which is its real limitation as a primary key. If you want both a sortable key and a short public identifier, use UUID v7 internally and a Nano ID externally, which also decouples your URLs from your database.
A practical default
For most applications: UUID v7 as the primary key, Nano ID for anything a user sees or types, and 21 characters minimum for anything that must not be guessed. Never shorten below the length your volume requires, never use Math.random(), and put a unique constraint on the column regardless, because it costs nothing and turns an improbable event into a handled one.