Every application needs unique identifiers. But with UUID v4, UUID v7, ULID, and NanoID all competing for adoption, choosing the right format isn't obvious. We tested each format across real-world scenarios to help you decide.
Why Your ID Format Matters
The identifier you choose affects:
- Database performance: Sortable IDs reduce index fragmentation by 40-60%
- URL aesthetics: A 36-character UUID looks different than a 21-character NanoID
- Debugging: Timestamp-embedded IDs tell you when a record was created at a glance
- Storage costs: At billions of rows, 15 fewer characters per ID adds up
The Four Contenders
UUID v4 — The Universal Default
UUID v4 is the format everyone knows. It's 36 characters of hex with dashes, completely random, and supported everywhere.
f47ac10b-58cc-4372-a567-0e02b2c3d479
How it works: 122 bits of randomness from crypto.randomUUID(), with 6 bits reserved for version (4) and variant (10xx).
Best for: Systems where compatibility matters more than performance. If you're integrating with APIs that expect UUIDs, this is the safe choice.
The problem: Random UUIDs cause B-tree index fragmentation in databases. New IDs scatter across the index instead of appending sequentially, leading to more page splits and slower inserts at scale.
UUID v7 — The Modern Upgrade
UUID v7 solves the biggest problem with v4: it embeds a 48-bit millisecond timestamp in the first 48 bits, making IDs naturally sortable by creation time.
018f3e5c-6a2b-7000-8000-1a2b3c4d5e6f
│ │
└── timestamp ─┘
How it works: First 48 bits = Unix timestamp in milliseconds. Remaining 74 bits = random. Version bits set to 7, variant to 10xx.
Best for: Database primary keys. UUID v7 gives you the universality of UUIDs with the sortability of auto-increment IDs. If you're starting a new project in 2025, this should be your default.
Real impact: PostgreSQL benchmarks show 2-3x faster bulk inserts with UUID v7 vs v4 due to sequential index writes.
ULID — The Readable Alternative
ULID (Universally Unique Lexicographically Sortable Identifier) takes a different encoding approach. Instead of hex, it uses Crockford's Base32 — a 32-character alphabet that excludes confusing characters like I/L/O/U.
01ARZ3NDEKTSV4RRFFQ69G5FAV
│ │
└── 10 chars timestamp ────┘── 16 chars random ──┘
How it works: 48-bit timestamp encoded as 10 Crockford Base32 characters + 80 bits of randomness encoded as 16 characters.
Best for: Systems where IDs are visible to humans — admin panels, log entries, support tickets. The shorter length and unambiguous characters make ULIDs easier to read, copy, and communicate verbally.
The encoding advantage: Crockford Base32 excludes I (confused with 1), L (confused with 1), O (confused with 0), and U (accidental profanity). You can safely read a ULID over the phone.
NanoID — The Compact Choice
NanoID is the minimalist option. At 21 characters using a URL-safe base64 alphabet (A-Za-z0-9_-), it's 40% shorter than a UUID.
V1StGXR8_Z5jdHi6B-myT
How it works: 21 random bytes masked to a 64-character alphabet using crypto.getRandomValues(). The default size gives ~126 bits of entropy.
Best for: URL slugs, short codes, client-side IDs, and anywhere string length is a constraint. NanoID is popular in frontend applications where IDs appear in URLs.
Size vs. safety: The default 21-character NanoID has a collision probability similar to UUID v4. But if you reduce the size (e.g., to 10 characters), collision risk increases significantly.
Head-to-Head Comparison
<!-- comparison -->| Feature | UUID v4 | UUID v7 | ULID | NanoID |
|---|---|---|---|---|
| Length | 36 chars | 36 chars | 26 chars | 21 chars |
| Sortable | No | Yes | Yes | No |
| Timestamp | No | Yes (ms) | Yes (ms) | No |
| Encoding | Hex | Hex | Crockford Base32 | Base64url |
| Standard | RFC 4122 | RFC 9562 | Spec (no RFC) | Spec (no RFC) |
| URL-safe | No (dashes) | No (dashes) | Yes | Yes |
| Case-sensitive | No | No | No | Yes |
| DB index perf | Poor | Excellent | Excellent | Poor |
| Human-readable | Fair | Fair | Good | Fair |
Real-World Decision Guide
Starting a new backend project? → UUID v7 It's the modern standard. Sortable, universally compatible with UUID columns, and database-friendly.
Building a user-facing feature? → ULID Shorter, more readable, and no ambiguous characters. Great for order numbers, ticket IDs, or anything users might need to reference.
Need short IDs for URLs? → NanoID At 21 characters, NanoIDs are compact enough for URL paths without sacrificing collision resistance.
Maintaining a legacy system? → UUID v4 If your schema already uses UUID v4, there's rarely a reason to migrate. The performance difference only matters at significant scale.
High-throughput database inserts? → UUID v7 or ULID Both are timestamp-prefixed, giving you sequential index writes. UUID v7 is better if your ORM/database expects UUID format.
Implementation Notes
All four formats can be generated purely in the browser using crypto.randomUUID() and crypto.getRandomValues(). No server calls or external libraries needed.
For server-side use:
- UUID v4/v7: Built into most languages (Python
uuid, Gogoogle/uuid, Rustuuid) - ULID: Libraries available for every major language (
ulidpackages) - NanoID: Official packages for JS, Python, Go, Rust, and 20+ languages
Our Recommendation
For most developers in 2025, UUID v7 is the right default. It gives you the universality of UUIDs — every database, ORM, and API understands them — with the performance benefits of sortable IDs. If you're in a position to choose freely, start with UUID v7 and only switch if you have a specific reason (shorter IDs, human readability, etc.).