UUID vs ULID vs NanoID: Choosing an ID Format

Compare UUID v4, UUID v7, ULID and NanoID for database primary keys and public identifiers — size, sortability, performance and security.

Last updated · 2026-09-20

Why not just auto-increment?

Sequential integers are compact and fast, but they leak information (order 1043 tells competitors your volume), make IDs guessable in URLs and require a central database to issue them. Distributed systems, offline-first apps and public APIs usually prefer random or time-based identifiers.

UUID v4

A UUID v4 is 128 bits with 122 random bits, written as 36 hex characters with hyphens. It is universally supported: PostgreSQL has a native uuid type and every language can generate one. The downside is randomness in indexes — inserts land all over the B-tree, causing page splits and poor cache locality on very large tables.

UUID v7

Standardized in RFC 9562, UUID v7 puts a 48-bit millisecond timestamp in the leading bits, followed by random data. It keeps the familiar UUID format and native column types while making new rows sort by creation time, which greatly improves insert performance. For new systems that want UUIDs, v7 is the best default.

ULID

A ULID is also 128 bits with a 48-bit timestamp, but encoded in 26 Crockford Base32 characters such as 01HF8Z3K6M9V2Q4R7T1W5X8Y0B. It is shorter than a UUID, URL-safe, case-insensitive and lexicographically sortable as a string. Our ULID decoder extracts the exact creation time from any ULID.

NanoID

NanoID generates short random strings from a URL-safe alphabet; the default 21 characters provide about 126 bits of randomness, comparable to UUID v4, in a much more compact form. It is ideal for public-facing IDs like share links, but it has no embedded timestamp and no native database type.

Quick recommendation

Internal primary keys: UUID v7 or ULID. Maximum compatibility: UUID v4. Short public URLs: NanoID. Never rely on any of these as secrets for password-reset links — use a longer token from a dedicated secure generator and store only its hash.

crypto.randomUUID();            // UUID v4
import { ulid } from "ulid"; ulid(); // ULID
import { nanoid } from "nanoid"; nanoid(); // NanoID

Try the tools from this article

← All tutorials