UUIDv7 vs ULID: Which Time-Sortable ID Should You Use?
Comparing two modern approaches to generating sortable, globally unique identifiers
TL;DR
- Both are 128-bit, time-sortable identifiers with a 48-bit millisecond timestamp + 80 bits of randomness
- UUIDv7 is an IETF standard (RFC 9562), uses hex-with-dashes format, and fits natively into database UUID columns
- ULID is a community spec, uses Crockford's Base32 (26 chars vs 36), and is case-insensitive
- Database support: UUIDv7 wins -- native UUID types in PostgreSQL, MySQL, etc. vs ULID needing CHAR(26) or BINARY(16)
- Ecosystem: UUIDv7 is compatible with the massive UUID tooling ecosystem; ULID has its own smaller ecosystem
- Choose UUIDv7 for new projects; choose ULID if you need shorter strings or already use it
Both UUIDv7 and ULID solve the same problem: generating globally unique, time-sortable identifiers without coordination. They share remarkably similar designs -- a 48-bit millisecond timestamp followed by random bits, all packed into 128 bits. So why do both exist, and which should you choose? The answer comes down to standardization, encoding, and ecosystem support.
At a Glance
UUIDv7 (RFC 9562)
01936b2e-1e85-7000-8000-4a6f7e3b2c1d
48-bit timestamp + version/variant bits + 62 random bits. IETF standard. Hex encoding with dashes. 36 characters.
ULID (Community Spec)
01ARZ3NDEKTSV4RRFFQ69G5FAV
48-bit timestamp + 80 random bits. Community specification. Crockford's Base32. 26 characters.
Detailed Comparison
| Feature | UUIDv7 | ULID |
|---|---|---|
| Standard | IETF RFC 9562 (2024) | Community specification |
| Format | Hex with dashes (8-4-4-4-12) | Crockford's Base32 |
| String length | 36 characters | 26 characters |
| Binary size | 128 bits (16 bytes) | 128 bits (16 bytes) |
| Timestamp bits | 48 bits (ms precision) | 48 bits (ms precision) |
| Random bits | 62 bits (6 used for ver/var) | 80 bits |
| Sortability | Lexicographic (string & binary) | Lexicographic (string & binary) |
| Native DB type | UUID (all major databases) | None (use CHAR/BINARY) |
| Case sensitivity | Case-insensitive (hex) | Case-insensitive (Base32) |
| Ecosystem | Massive (UUID compatible) | Growing but smaller |
Format Deep Dive
Despite their different string representations, UUIDv7 and ULID have remarkably similar internal structures. Both dedicate their first 48 bits to a Unix millisecond timestamp, followed by random data.
UUIDv7 Layout (128 bits)
6 bits used for version (0111) and variant (10) markers, leaving 74 bits of randomness
ULID Layout (128 bits)
No version/variant overhead -- all 80 remaining bits are random
Key difference: UUIDv7 reserves 6 bits for version and variant markers (required by the UUID standard), which means 62 usable random bits per millisecond. ULID uses all 80 remaining bits for randomness. In practice, both provide far more than enough uniqueness -- 62 random bits means over 4.6 quintillion possible values per millisecond.
Ecosystem Support
One of the most significant practical differences is ecosystem compatibility. UUIDv7 benefits from decades of UUID infrastructure.
UUIDv7 Ecosystem
- +Native UUID column type in PostgreSQL, MySQL, SQL Server, etc.
- +PostgreSQL 17+ native generation (gen_random_uuid_v7)
- +Compatible with every UUID library, ORM, and API
- +Supported in Go (google/uuid), Java (java.util.UUID), .NET, and more
- +Standard UUID validation and parsing works unchanged
ULID Ecosystem
- +Libraries in JavaScript, Python, Go, Java, Ruby, Rust, and more
- +Shorter string representation (26 vs 36 characters)
- xNo native database column type
- xRequires custom validation logic
- xNot recognized by standard UUID-aware tools and APIs
When to Use Each
Choose UUIDv7 When...
- Database primary keys -- native UUID column type for efficient storage
- Interoperability -- APIs, microservices, or systems that already use UUIDs
- Standards compliance -- IETF RFC 9562 gives long-term stability
- New projects -- modern default with the strongest ecosystem support
- PostgreSQL 17+ -- take advantage of native gen_random_uuid_v7()
- ORM compatibility -- every ORM supports UUID fields out of the box
Choose ULID When...
- String length matters -- 26 chars vs 36 for URLs, logs, or display
- Existing ULID usage -- already adopted in your stack with no issues
- No UUID column available -- using a datastore without native UUID support
- Maximum randomness -- 80 random bits vs 62 (rarely a practical concern)
- URL-friendly IDs -- no dashes, case-insensitive, shorter representation
Code Examples
// UUIDv7 — npm install uuidv7
import { uuidv7 } from "uuidv7";
const id1 = uuidv7();
// "01936b2e-1e85-7000-8000-4a6f7e3b2c1d"
// ULID — npm install ulid
import { ulid } from "ulid";
const id2 = ulid();
// "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// Extract timestamp from either:
// UUIDv7: first 12 hex chars (48 bits)
const tsV7 = parseInt(
id1.replace(/-/g, "").slice(0, 12), 16
);
// ULID: first 10 Base32 chars (48 bits)
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
let tsUlid = 0;
for (const c of id2.slice(0, 10)) {
tsUlid = tsUlid * 32 + CROCKFORD.indexOf(c);
}# UUIDv7 — pip install uuid7
from uuid_extensions import uuid7
id_v7 = uuid7()
# UUID('01936b2e-1e85-7000-8000-4a6f7e3b2c1d')
# ULID — pip install python-ulid
from ulid import ULID
id_ulid = ULID()
# ULID(01ARZ3NDEKTSV4RRFFQ69G5FAV)
# Both provide timestamp extraction:
print(id_ulid.timestamp()) # float epoch
# UUIDv7 timestamp:
ts = int(str(id_v7).replace('-', '')[:12], 16)-- UUIDv7: native UUID column, native generation (PG 17+)
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid_v7(),
name TEXT NOT NULL
);
-- ULID: no native type — use CHAR(26) or BINARY
CREATE TABLE events_ulid (
id CHAR(26) PRIMARY KEY, -- or BYTEA for binary
name TEXT NOT NULL
);
-- Must generate ULID in application code
-- No native ULID functions in PostgreSQL// UUIDv7 — github.com/google/uuid
import "github.com/google/uuid"
id, _ := uuid.NewV7()
// 01936b2e-1e85-7000-8000-4a6f7e3b2c1d
// ULID — github.com/oklog/ulid/v2
import "github.com/oklog/ulid/v2"
id := ulid.Make()
// 01ARZ3NDEKTSV4RRFFQ69G5FAV
// Both sort correctly as strings
// UUIDv7 also sorts correctly in binary (UUID type)Frequently Asked Questions
Try Our UUIDv7 Tools
Generate UUIDv7 identifiers or extract timestamps from existing UUIDs.