Skip to content

UUID v4 vs v7 vs ULID: Choosing the Right ID Format in 2026

For a long time UUID v4 was the default answer to "what should I use for my primary keys?". In 2026 that answer has changed. RFC 9562 standardized UUID v7, which keeps the collision safety of v4 but adds time-ordering. ULID has been a popular third-party alternative for years. This guide compares all three, explains the trade-offs that matter in production, and gives you a clear recommendation for different project types.

A Quick Look at All Three

All three formats fit in 128 bits, so they all take 16 bytes on disk. The difference is what those bits encode.

UUID v4:  f47ac10b-58cc-4372-a567-0e02b2c3d479
UUID v7:  01963c1a-7e3f-7b89-a123-45c6d7e8f901
ULID:     01HE9GZ7VN8K4P2Q3R5T6V7X8Y

UUID v4 is 122 bits of random data with 6 bits reserved for version and variant markers. UUID v7 replaces the first 48 bits with a Unix timestamp in milliseconds, then fills the rest with randomness. ULID uses the same concept as v7 but encodes the whole value in Crockford base32, giving a shorter string that sorts lexicographically.

UUID v4: The Established Default

UUID v4 is purely random. It has been the workhorse of distributed systems for two decades because two services can independently generate IDs with a negligible chance of collision. The math is reassuring. With 122 bits of randomness, you would have to generate about 2.71 quintillion UUIDs before there is a 50% chance of a single collision (the birthday bound).

The problem only shows up once you use UUID v4 as a primary key in a database with a clustered index, which is the default in MySQL InnoDB and SQL Server. Each new random UUID lands in a random position in the index, causing B-tree page splits, cache churn, and fragmentation. On write-heavy tables this can cut insert throughput by 50% or more compared to an auto-increment integer.

PostgreSQL uses a heap table with a separate index, which is less sensitive to UUID randomness, but even there the index loses cache locality compared to sequential keys.

// Node.js
import { randomUUID } from "crypto";
const id = randomUUID(); // v4 by default

UUID v7: The Modern Default

UUID v7 was standardized in May 2024 as part of RFC 9562. The structure is 48 bits of Unix timestamp in milliseconds, 12 bits of sub-millisecond precision or random data, then 62 bits of randomness with version and variant markers. The critical property is that two UUIDs generated in order will sort in the same order. An ID generated one millisecond later will always be greater.

This time-ordering gives you the cache locality of sequential IDs with the distributed safety of UUID v4. Inserts append to the end of the B-tree index, page splits become rare, and the working set that the database needs to keep in memory shrinks dramatically. In benchmarks, v7 typically performs within a few percent of auto-increment integers on insert-heavy workloads, while v4 can be 30-70% slower on the same hardware.

// Node.js (uuid package 10+)
import { v7 as uuidv7 } from "uuid";
const id = uuidv7();
// 01963c1a-7e3f-7b89-a123-45c6d7e8f901

// PostgreSQL 18+ has native uuidv7()
// SELECT uuidv7();

The one trade-off is that the creation timestamp is visible in the ID. This is usually fine, even desirable for debugging, but if you need opaque IDs (for example to prevent customers from guessing the rate at which you issue records), use v4 or rotate through a hash.

ULID: The Popular Third-Party Alternative

ULID predates UUID v7 and solved the same problem in a slightly different way. It uses a 48-bit millisecond timestamp followed by 80 bits of randomness, encoded in Crockford base32. The result is 26 characters long (versus 36 for a UUID), URL-safe without percent-encoding, and still sortable lexicographically.

// Node.js
import { ulid } from "ulid";
const id = ulid();
// 01HE9GZ7VN8K4P2Q3R5T6V7X8Y

Strengths: 26 characters is noticeably shorter than a UUID string, and Crockford base32 skips ambiguous characters (I, L, O, U) so it is painless to copy from logs. Weaknesses: ULID is not an IETF standard, so database and language ecosystems do not treat it as a first-class type. Storing a ULID typically means either a 16-byte binary column or a 26-character text column, and you lose the native uuid type integration.

Side-by-Side Comparison

PropertyUUID v4UUID v7ULID
Bits128128128
String length363626
Time-orderedNoYesYes
StandardRFC 9562RFC 9562Spec only
DB native typeuuiduuidbinary or text
Reveals timeNoYes (ms)Yes (ms)
Index friendlyPoorExcellentExcellent

Which Should You Pick?

The decision usually comes down to three questions. First, do you control the database? Second, is the ID ever exposed to end users? Third, does your language and ORM ecosystem support the format natively?

  • Pick UUID v7 for almost every new project. It is standardized, has first-class support in PostgreSQL 18, MySQL via the uuid type, and every major language has a library. You get the index-friendly performance of a sequential key with the collision safety of a UUID.
  • Pick UUID v4 when the ID must be completely unpredictable and time information is sensitive. Think invite tokens, share links, or primary keys in systems where you do not want competitors to infer your growth rate from ID ranges.
  • Pick ULID when the ID appears in URLs and shorter strings matter, or when you are already committed to the ULID ecosystem. If you are starting fresh in 2026, v7 is probably the better call because of the IETF standardization.
  • Stick with bigint auto-increment for small, centralized systems where you know you will never need distributed generation. It is still the fastest and smallest option.

Extracting the Timestamp

Both v7 and ULID encode the creation time. You can recover it for debugging or log correlation.

// UUID v7 -> timestamp in ms
function v7Timestamp(uuid) {
  const hex = uuid.replace(/-/g, "").slice(0, 12);
  return parseInt(hex, 16);
}

// ULID -> timestamp in ms
import { decodeTime } from "ulid";
decodeTime("01HE9GZ7VN8K4P2Q3R5T6V7X8Y");

Migration: v4 to v7

If you have an existing v4-keyed system feeling index pressure, you do not have to rewrite history. Keep old rows as v4 and generate new rows as v7. Both share the same uuid column type, the version is encoded in the value itself. New inserts will cluster at the end of the index, gradually defragmenting the index over time as pages are rewritten. You get most of the win with almost zero migration effort.

Security Notes

None of these are cryptographic secrets. If you need tokens that must not be guessed, do not use any UUID variant as the token itself. Generate a cryptographically secure random string (at least 256 bits) and store it separately. Use UUIDs for identity and random tokens for authentication.

Generate UUIDs instantly

Need a UUID right now? Use our free UUID Generator to create v4 UUIDs in the browser, in bulk, with optional uppercase and brace formatting. No data leaves your computer.

Open UUID Generator