UUID v7 Generator
Generate time-ordered UUID v7 for database primary keys
…
Format
What is UUID v7?
UUID v7 is a next-generation UUID format standardised in RFC 9562 (May 2024). It encodes a 48-bit Unix millisecond timestamp in the most-significant bits, followed by version and variant markers, and fills the remaining bits with cryptographically secure random data.
Because the timestamp occupies the high bits, UUID v7 values sort chronologically — both lexicographically and numerically. This makes them an excellent fit for database primary keys, where random UUIDs (v4) cause B-tree index fragmentation.
Why Random UUIDs Fragment Database Indexes
B-tree indexes — used by PostgreSQL, MySQL, SQLite, and most other databases — keep rows sorted by key value. When you insert a new row, the database must place it in the correct sorted position within the index.
With UUID v4 (fully random), each new insert lands at an essentially random position in the index tree. This forces the database to read and rewrite internal index pages constantly, splitting full pages and leaving others half-empty. The result is a fragmented, bloated index that slows both writes and reads as the table grows.
UUID v7 Bit Layout
UUID v7 is 128 bits wide, divided into six fields:
| Bits | Field | Purpose |
|---|---|---|
| 48 | unix_ts_ms | 48-bit Unix timestamp in milliseconds — occupies the entire high half |
| 4 | ver | Version number — always 0111 (decimal 7) |
| 12 | rand_a | 12 bits of cryptographically secure random data |
| 2 | var | Variant marker — always 10 (RFC 4122 variant) |
| 62 | rand_b | 62 bits of cryptographically secure random data |
The timestamp precision is 1 millisecond. Within the same millisecond, UUID v7 values are ordered by their random suffix — they are not guaranteed to be monotonically increasing sub-millisecond, but they are k-sortable: IDs generated close together in time will sort close together in the index.
UUID v7 vs UUID v1
Both UUID v1 and UUID v7 embed a timestamp, but they differ significantly in design:
| Feature | UUID v7 | UUID v1 |
|---|---|---|
| Epoch / Time Base | Unix epoch (Jan 1, 1970) | Gregorian epoch (Oct 15, 1582) |
| Time Precision | 1 millisecond | 100 nanoseconds |
| Sortable | Yes — k-sortable by design | No — time fields are scrambled in the UUID layout |
| Privacy | No host information leaked | Embeds MAC address of generating host |
| DB Index Performance | Excellent — sequential inserts, minimal fragmentation | Poor — non-sequential despite timestamp |
| Standard | RFC 9562 (2024) | RFC 4122 (2005) |
| Native Browser Support | Not yet (no crypto.randomUUID v7) | Not available natively |
For any new project that needs time-ordered UUIDs, prefer UUID v7 over UUID v1. UUID v1 is legacy and leaks host information.
UUID v7 vs ULID
ULID (Universally Unique Lexicographically Sortable Identifier) solves a similar problem to UUID v7. Here is how they compare:
- Follows the RFC 9562 UUID standard — compatible with all UUID tooling
- Hyphenated hex format — universally recognized
- Native database UUID column support
- 128 bits total
- Crockford Base32 encoding — 26 characters, slightly more compact
- Case-insensitive and avoids ambiguous characters (I, L, O, U)
- More human-readable at a glance
- Requires a library — no native platform support
If you are already in a UUID ecosystem (PostgreSQL uuid column, REST APIs returning UUIDs), use UUID v7. If you are starting fresh and prefer a more human-friendly encoding, ULID is a reasonable alternative.
Using UUID v7 in Databases
UUID v7 is not yet natively generated by most databases, but it can be stored in standard UUID columns and generated in application code or via extensions:
uuid column. The pg-uuidv7 extension adds a uuid_generate_v7() server-side function if you need DB-generated IDs.BINARY(16) or CHAR(36) column. Generate in application code. MySQL 8.0+ has ordered UUID support via UUID_TO_BIN(UUID(), 1) for v1, but v7 requires app-level generation.TEXT (36 chars) or BLOB (16 bytes). Generate in application code. Lexicographic sort on TEXT works correctly because UUID v7 uses a fixed-width timestamp prefix.Code Examples
UUID v7 is not yet available via crypto.randomUUID(). Use a library such as uuidv7 (npm) until native support arrives:
function generateUuidV7() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
// Embed 48-bit Unix ms timestamp
buf[0] = Number((ms >> 40n) & 0xFFn)
buf[1] = Number((ms >> 32n) & 0xFFn)
buf[2] = Number((ms >> 24n) & 0xFFn)
buf[3] = Number((ms >> 16n) & 0xFFn)
buf[4] = Number((ms >> 8n) & 0xFFn)
buf[5] = Number(ms & 0xFFn)
// Set version 7 (0111xxxx)
buf[6] = (buf[6] & 0x0F) | 0x70
// Set variant (10xxxxxx)
buf[8] = (buf[8] & 0x3F) | 0x80
const hex = [...buf].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`
}
// Node.js 20+ built-in
// import { randomUUID } from 'node:crypto' // v4 only — no v7 yet in stdlib# pip install uuid7 import uuid_extensions uid = uuid_extensions.uuid7() print(uid) # e.g. 018e2b3d-1a2b-7000-8000-abc123456789 print(uid.time) # Unix ms timestamp embedded in the UUID # Or as a plain string from uuid_extensions import uuid7str print(uuid7str())
-- PostgreSQL 13+ extension-free implementation
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS uuid
LANGUAGE sql
AS $$
SELECT encode(
set_bit(
set_bit(
overlay(
uuid_send(gen_random_uuid())
PLACING substring(int8send(floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint) FROM 3)
FROM 1 FOR 6
),
52, 1
),
53, 1
),
'hex'
)::uuid;
$$;
-- Usage as a default primary key
CREATE TABLE events (
id uuid PRIMARY KEY DEFAULT uuid_generate_v7(),
payload jsonb,
created_at timestamptz DEFAULT now()
);function extractTimestamp(uuid: string): Date {
const hex = uuid.replace(/-/g, '')
const ms = parseInt(hex.slice(0, 12), 16) // first 48 bits = ms timestamp
return new Date(ms)
}
const uid = '018e2b3d-1a2b-7000-8000-abc123456789'
console.log(extractTimestamp(uid).toISOString())
// → "2024-03-15T10:22:05.259Z"