The ID you pick shows up everywhere
Identifiers leak into your URLs, your JSON payloads, your database indexes, and your logs — so the format you choose has real ergonomic and performance consequences. "Just use a UUID" is a fine default, but UUID v4 is random, ugly in URLs, and can hurt database write performance at scale. ULID and nanoid exist to fix specific pain points UUID v4 has. Here's how the three compare and when each is the right call. For the two UUID versions specifically, see UUID v4 vs v7.
UUID: the universal standard
A UUID is a 128-bit identifier, written as 36 characters: f47ac10b-58cc-4372-a567-0e02b2c3d479. Its strength is ubiquity — every language, database, and framework understands UUIDs natively, and crypto.randomUUID() generates v4 in one call.
- UUID v4 is fully random. That's great for unguessability but bad for databases: random values scatter inserts across a B-tree index, causing page splits and fragmentation that slow high-volume writes.
- UUID v7 fixes exactly that by making IDs time-ordered — a millisecond timestamp prefix plus randomness — so new rows insert at the "end" of the index and stay roughly sortable by creation time. In 2026, v7 is the recommended default for database primary keys.
The downsides that remain: 36 characters is long, the hyphens and format aren't URL-pretty, and it's not the most compact option.
ULID: sortable and more compact
A ULID (Universally Unique Lexicographically Sortable Identifier) is also 128 bits but encoded as 26 Crockford Base32 characters: 01ARZ3NDEKTSV4RRFFQ69G5FAV. It's built around two properties UUID v4 lacks:
- Lexicographically sortable. The first 48 bits are a timestamp, so sorting ULIDs *as strings* sorts them by creation time — no separate
created_atneeded for ordering. - More compact and URL-safe. 26 characters vs 36, no hyphens, case-insensitive Base32, and it's safe to drop into a URL.
ULID overlaps heavily with UUID v7 (both are time-ordered 128-bit IDs). The practical difference: UUID v7 keeps the standard UUID format (so your database's native uuid type and tooling just work), while ULID gives you a shorter, prettier string at the cost of not being a "real" UUID. If your stack has first-class UUID support, v7 is usually the smoother choice; if you want compact, human-manageable IDs in URLs, ULID is attractive.
nanoid: tiny, random, configurable
nanoid takes a different goal: the *smallest* URL-friendly random ID. It's not 128 bits by default — you choose the length. The default is 21 characters from a 64-symbol URL-safe alphabet (V1StGXR8_Z5jdHi6B-myT), which gives a collision resistance comparable to UUID v4 in far fewer bytes.
- Compact and URL-safe by design — ideal for public-facing short IDs, share links, and slugs.
- Configurable — pick your own alphabet (e.g. only lowercase, or no look-alike characters) and length to trade size against collision odds.
- Purely random, so like UUID v4 it is not sortable and not time-ordered.
nanoid is the pick when you want short, opaque, random IDs and don't need time ordering.
Side-by-side
| UUID v4 | UUID v7 | ULID | nanoid | |
|---|---|---|---|---|
| Length | 36 chars | 36 chars | 26 chars | 21 (configurable) |
| Bits | 128 | 128 | 128 | configurable |
| Time-ordered / sortable | No | Yes | Yes | No |
| URL-friendly | So-so | So-so | Yes | Yes |
| DB primary key | Poor at scale | Excellent | Good | OK |
| Native DB support | Universal | Growing | Via string | Via string |
| Best for | Legacy default | DB keys | Sortable, compact IDs | Short public IDs |
How to choose
- Database primary key at scale? → UUID v7 (time-ordered, native UUID type, no index fragmentation). ULID if you specifically want the shorter string form.
- Short, opaque, public-facing ID (share links, slugs)? → nanoid.
- Need creation-time ordering baked into the ID? → UUID v7 or ULID.
- Maximum compatibility, don't care about the downsides? → UUID v4 — still a perfectly reasonable default for low write volume.
The one habit worth breaking is reaching for UUID v4 as a database key by reflex — at high insert rates, v7 or ULID will save you real index pain. To generate identifiers now, use the UUID generator; for the timestamp mechanics behind v7 and ULID, see Unix timestamps.