UUID vs Nanoid
On this page
Nanoid is a small library that generates short, URL-friendly, random IDs. It’s not a UUID — it’s a different design with different trade-offs.
TL;DR. Use a UUID for anything that needs the universal standard (database keys, cross-system IDs, anything you’ll regret if it conflicts with another system). Use nanoid for public-facing short IDs where you control the alphabet and want pretty URLs (e.g. shortener slugs, share links, public IDs that hide the database key).
At a glance
| UUID v4 | Nanoid (default) | |
|---|---|---|
| Length | 36 chars (with hyphens) | 21 chars |
| Bits | 122 random | 126 random |
| Alphabet | 16 (hex) | 64 (URL-safe) |
| Format | xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx | V1StGXR8_Z5jdHi6B-myT |
| Standard | RFC 4122 / 9562 | npm package, ad-hoc |
| Database native type | yes | no (stored as text) |
| Time-ordered version | yes (v7) | no |
| Customizable alphabet | no | yes |
Why nanoid is shorter
UUID uses base 16 (hex) and adds hyphens. Nanoid uses base 64 (URL-safe alphanumerics + _ and -). With more characters per byte of information, you get a shorter string for the same randomness budget.
122 bits in base 16: ⌈122/4⌉ = 31 hex chars (UUID adds 4 hyphens → 35 visible)
126 bits in base 64: ⌈126/6⌉ = 21 chars
Nanoid actually has more random bits (126 vs 122) in fewer characters, because UUID burns 6 bits on the version + variant metadata.
Length vs. recognizability
| Length | Example | Trade-off |
|---|---|---|
| 36 (UUID) | 0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b | Universally recognized as an ID |
| 21 (nanoid) | V1StGXR8_Z5jdHi6B-myT | Looks like random gibberish to a reader |
| 8 (custom nanoid) | aB3cD4eF | Pretty URL — but 48 bits, collision risk |
| 4 (custom nanoid) | aB3c | URL slug territory — must check for collisions |
UUIDs in URLs scream “this is an internal ID.” Short nanoids look like part of the brand. Use this distinction.
Where nanoid wins
- Public URLs.
myapp.com/u/V1StGXR8is friendlier thanmyapp.com/u/0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b. - QR codes / SMS. Shorter strings = denser QR codes, fewer SMS characters.
- Share tokens. “Share this 12-char code” is easier than “share this UUID.”
- Custom alphabets. Need to exclude
0/Oand1/l/I? Nanoid lets you specify the alphabet.
Where UUID wins
- Database primary keys. Use the native
uuidtype — half the storage of a string column, faster index, validated format. - Cross-system identity. Two services that both generate UUIDs will never collide. Two services that both generate 8-char nanoids almost certainly will.
- Logs and observability. A UUID in a log is unambiguously an ID; a short string could be anything.
- Time-ordering. UUID v7 sorts chronologically; nanoid is purely random.
- Tooling and frameworks. UUIDs are first-class in every ORM, every router, every IDE.
Pattern: UUID internal, nanoid public
The cleanest design uses both:
users
├── id uuid primary key (v7)
├── public_id text nanoid for URLs ("V1StGXR8")
└── ...
- The
uuidis what your code, foreign keys, and joins use. - The
public_idis what you put in URLs and share tokens. - The two are decoupled, so leaking one (e.g. a public ID in a screenshot) doesn’t expose the other.
Code
Nanoid
import { nanoid, customAlphabet } from "nanoid";
nanoid(); // "V1StGXR8_Z5jdHi6B-myT" — 21 chars
nanoid(8); // "aB3cD4eF" — 8 chars
nanoid(8, /* not the third arg, this is wrong */);
// Custom alphabet (no confusing 0/O/1/l/I)
const code = customAlphabet("23456789ABCDEFGHJKLMNPQRSTUVWXYZ", 6);
code(); // "X4M2KP"
UUID
import { v4, v7 } from "uuid";
v4(); // "0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b"
v7(); // "01928a47-3b30-7c5e-9d1a-f0b8c4a7e923"
Collision math for short nanoids
The whole point of UUID’s 122 random bits is “you’ll never collide.” Nanoid lets you go shorter, which means you have to check.
For a nanoid of length L over alphabet size A, the birthday-paradox 1% threshold (~1% chance of any collision) is:
| L | Alphabet | 1% threshold |
|---|---|---|
| 8 chars | 64 | ~21,000 IDs |
| 10 chars | 64 | ~1.3 million IDs |
| 12 chars | 64 | ~85 million IDs |
| 14 chars | 64 | ~5.5 billion IDs |
| 21 chars (default) | 64 | ~10²⁰ IDs (effectively zero) |
If you go below the default 21, add a unique constraint to your database column and retry on collision. The retry rate is tiny but non-zero.
Don’t try this
- ❌ “I’ll just take the first 8 chars of a UUID.” That’s 32 random bits — collision-prone at modest volumes.
- ❌ “I’ll use nanoid as a primary key.” It works, but you lose the native DB type, ordering, and ecosystem support. Use UUID for keys.
- ❌ “I’ll use nanoid as a security token.” Nanoid uses a CSPRNG by default, so this is fine in principle — but a UUID v4 is just as secure and more recognizable.
Decision matrix
What's the ID for?
├── DB primary key → UUID (v7 if greenfield, v4 if not)
├── Public URL slug → nanoid (custom length, no confusing chars)
├── Share / invite token → nanoid
├── API key → 32+ char nanoid OR signed JWT (not raw UUID)
├── Cross-service correlation → UUID (logs, traces — std format wins)
├── Cookie / session → signed token, not a raw ID of either kind
└── Generic identifier with no specific need → UUID (boring is good)
Try the tools
- UUID generator — for the times you need a real UUID
- Bulk UUID generator — generate test data in bulk
- UUID v7 generator — for primary keys
- UUID vs ULID — for the “I want sortable, but shorter” case