Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

UUID vs ULID

On this page
  1. What they are
  2. Side-by-side
  3. Where ULID wins
  4. Where UUID v7 wins
  5. Storage comparison
  6. Performance — does ULID’s extra randomness matter?
  7. Migration patterns
  8. Code
    1. UUID v7
    2. ULID
  9. Decision flowchart
  10. What about UUID v6?
  11. Try the tools

TL;DR. ULID and UUID v7 solve the same problem (time-ordered identifiers) using different encodings. ULID is shorter (26 chars vs 36) and case-sensitive Crockford-base32; UUID v7 is the universal standard with broader ecosystem support. For new code, prefer UUID v7 unless you specifically benefit from ULID’s shorter strings.

What they are

UUID v7ULID
SpecRFC 9562 (May 2024, IETF standard)ulid spec (community spec, 2016)
Bits128128
Time48-bit Unix-ms timestamp (front)48-bit Unix-ms timestamp (front)
Random74 bits80 bits
Encodinghyphenated hex (xxxxxxxx-xxxx-...)Crockford base32 (01ARZ3NDEKTSV4RRFFQ69G5FAV)
String length36 chars (32 + 4 hyphens)26 chars
Sortableyes (lexicographic = chronological)yes (lexicographic = chronological)
Native DB typeyes (Postgres uuid, SQL Server uniqueidentifier, etc.)no (stored as char(26) or bytea(16))
Standard library supportbroad (every language)narrow (npm packages, but no stdlib)

Side-by-side

UUID v7:  01928a47-3b30-7c5e-9d1a-f0b8c4a7e923   ← 36 chars
ULID:     01HKZG3WQM7C2YMGRRPS9C4Z8J             ← 26 chars

Both encode 128 bits. Both put a millisecond timestamp at the front. The difference is purely how they’re written down.

Where ULID wins

Where UUID v7 wins

Storage comparison

ApproachBytes per IDNotes
Postgres uuid (v4 or v7)16Native, indexed, validated
Postgres text storing ULID string~27Includes length prefix
Postgres bytea(16) storing ULID bytes16 + overheadSame size as uuid, but no validation
SQL Server uniqueidentifier (Guid)16Native
SQL Server char(26) storing ULID26Fixed-width, slower index

The “ULID is shorter” advantage disappears once you store as bytes. For DB primary keys, UUID v7’s native type wins.

Performance — does ULID’s extra randomness matter?

Both v7 and ULID have a millisecond timestamp prefix. Within a single millisecond, ULID has 80 random bits, v7 has 74. Birthday-paradox 50% collision in one ms:

Neither will ever happen in real applications. Some v7 implementations add a monotonic counter for the same-millisecond case, eliminating even theoretical concerns.

Migration patterns

If you’re starting today and the choice is fresh:

new project + Postgres / SQL Server / MySQL  →  UUID v7
new project + DynamoDB / S3 keys             →  ULID (string-native, shorter)
existing project on UUIDs                    →  switch v4 → v7 default
existing project on ULIDs                    →  stay (no benefit to migrate)

For DynamoDB and S3 specifically, ULID’s shorter string and lack of hyphens makes for slightly more readable keys. For everywhere else, UUID v7 is the boring correct choice.

Code

UUID v7

import { v7 } from "uuid";
v7(); // "01928a47-3b30-7c5e-9d1a-f0b8c4a7e923"

ULID

import { ulid } from "ulid";
ulid(); // "01HKZG3WQM7C2YMGRRPS9C4Z8J"

Both are one-line generators. The library and the output format are the only difference.

Decision flowchart

Are you on Postgres / SQL Server / MySQL with native UUID columns?
├── Yes → UUID v7 (use the native type)
└── No, you're on DynamoDB / Redis / KV stores
    ├── Do you need string-native, no-hyphens keys? → ULID
    └── Otherwise → UUID v7 (still fine, just stored as 16-byte blob)

Does your team / framework already have UUIDs everywhere?
└── Yes → UUID v7 (don't fragment your codebase)

Are you greenfield with no constraints?
└── UUID v7 (the default carries less ecosystem risk)

In practice, UUID v7 wins about 90% of the time.

What about UUID v6?

v6 is “v1 with the timestamp bytes reordered to be sortable.” It’s standardized in RFC 9562 alongside v7. Don’t use v6. It exists for back-compat with systems that need to migrate from v1; v7 is the modern choice.

Try the tools