UUID v7 generator
Time-ordered UUIDs to RFC 9562, monotonic within a millisecond, with the embedded timestamp decoded for you.
Why version 7 exists
A version 4 UUID is pure randomness, which makes it a poor primary key. Databases store rows in a B-tree ordered by that key. Random keys mean every insert lands at an unpredictable point in the tree, so pages split, the working set stops fitting in cache, and the index fragments. On a large table the difference is measurable in both insert throughput and index size.
Version 7 fixes this without giving up the properties that made UUIDs useful. The first 48 bits are a big-endian Unix millisecond timestamp, so values generated later are numerically larger. Inserts land at the right-hand edge of the index, the same place a sequential integer would put them, while the remaining 74 random bits keep the value unguessable enough for most purposes and keep generation coordination-free.
RFC 9562 standardised it in May 2024, replacing RFC 4122. Native support is arriving: PostgreSQL 18 ships uuidv7(), .NET 9 has Guid.CreateVersion7(), and google/uuid added NewV7 in 1.6.0. JavaScript has none yet, which is why the implementation is spelled out below.
Monotonicity, and why it matters here
The timestamp only resolves to the millisecond. Two UUIDs created inside the same millisecond have nothing but randomness to separate them, so their relative order is arbitrary. For a system inserting thousands of rows per second, that is most of them.
RFC 9562 section 6.2 addresses this by allowing a counter in the 12-bit rand_a field, incremented for each value generated within the same millisecond. This generator does that: values produced in a burst still sort in the order they were created.
It is worth checking whether your library does. Several popular v7 implementations fill rand_a with fresh randomness on every call, which satisfies the letter of the specification but gives up in-millisecond ordering, which is the reason most people reach for v7 in the first place.
Sorting v7 UUIDs as text and sorting them as 128-bit integers give the same order, because the hexadecimal rendering is big-endian and fixed width. That is not true of version 1, where the timestamp fields are stored out of order.
What version 7 gives away
The creation timestamp is readable by anyone holding the value. Paste one into the field above and it decodes to the millisecond. That is a feature inside your own database and a leak on a public URL.
Concretely: if user IDs are v7 UUIDs and they appear in profile URLs, anyone can determine exactly when each account was created, order your users by signup time, and estimate your growth rate from a sample of profile links. If that matters, either keep v7 internal and expose a separate opaque identifier, or use v4 for the public-facing value.
The 74 random bits are still plenty against guessing. The concern is disclosure of the timestamp, not guessability of the identifier.
Version 7 against the alternatives
ULID solved the same problem before the RFC existed, with a 48-bit timestamp and 80 random bits in 26 Crockford Base32 characters. It is shorter to read and its alphabet excludes look-alike characters, but it is not a UUID, so a uuid column will not take it without conversion.
A bigint identity column beats both on raw insert performance and storage, and it is the right answer for a single-database application. It stops being the right answer when identifiers must be generated before insert, on a client, or across shards that cannot coordinate.
| Format | Bits | Characters | Sorts by time | Fits a uuid column |
|---|---|---|---|---|
| UUID v4 | 128 | 36 | No | Yes |
| UUID v7 | 128 | 36 | Yes, to the millisecond | Yes |
| ULID | 128 | 26 | Yes, to the millisecond | After conversion |
| bigint identity | 64 | Up to 19 | Yes | No |
Doing this in your own code
JavaScript
// v4, built in since Node 19 and in every current browser.
crypto.randomUUID();
// Web Crypto has no v7. 48-bit big-endian millisecond timestamp,
// then 74 random bits around the version and variant fields.
function uuidv7() {
const b = crypto.getRandomValues(new Uint8Array(16));
const ts = Date.now();
b[0] = (ts / 2 ** 40) & 0xff;
b[1] = (ts / 2 ** 32) & 0xff;
b[2] = (ts / 2 ** 24) & 0xff;
b[3] = (ts / 2 ** 16) & 0xff;
b[4] = (ts / 2 ** 8) & 0xff;
b[5] = ts & 0xff;
b[6] = (b[6] & 0x0f) | 0x70; // version 7
b[8] = (b[8] & 0x3f) | 0x80; // variant 10
const h = [...b].map((v) => v.toString(16).padStart(2, "0")).join("");
return `${h.slice(0,8)}-${h.slice(8,12)}-${h.slice(12,16)}-${h.slice(16,20)}-${h.slice(20)}`;
} crypto.randomUUID() only ever produces v4, in the browser and on Node alike. Node does now ship a native v7 as randomUUIDv7() on node:crypto, added in 24.16.0 and 26.1.0, but there is still no v7 anywhere in the Web Crypto API.
Python 3.6+
import uuid
uuid.uuid4() # random
uuid.uuid5(uuid.NAMESPACE_DNS, "example.com") # name based, deterministic
# Python 3.14 added uuid6(), uuid7() and uuid8() to the standard library.
uuid.uuid7()
# On 3.13 and earlier v7 needs a library:
# pip install uuid6
from uuid6 import uuid7
uuid7() uuid.uuid4() draws from os.urandom, so it is cryptographically secure. uuid.uuid8() is documented as not using a CSPRNG by default, so it is not a drop-in replacement for uuid4().
PostgreSQL
-- v4, built in since PostgreSQL 13.
SELECT gen_random_uuid();
-- v7 is native from PostgreSQL 18.
SELECT uuidv7();
-- As a primary key, v7 keeps inserts at the right edge of the index
-- instead of scattering them the way v4 does.
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT uuidv7(),
placed_at timestamptz NOT NULL DEFAULT now(),
total_cents integer NOT NULL
); Go
import "github.com/google/uuid"
uuid.New() // v4, panics on entropy failure
uuid.NewRandom() // v4, returns an error instead
uuid.NewV7() // v7, added in google/uuid v1.5.0 Java 17+
import java.util.UUID;
UUID.randomUUID(); // v4, backed by SecureRandom
// v7 is not in the JDK. java-uuid-generator, artifact
// com.fasterxml.uuid:java-uuid-generator:5.1.0
import com.fasterxml.uuid.Generators;
Generators.timeBasedEpochGenerator().generate(); // returns java.util.UUID C# / .NET 9+
Guid.NewGuid(); // v4
Guid.CreateVersion7(); // v7, added in .NET 9
// Watch the byte order. Guid.ToByteArray() returns the first three
// fields little-endian, which is the classic mixed-endian GUID trap
// when moving values between .NET and anything else.
Guid.NewGuid().ToByteArray(bigEndian: true); The mixed-endian layout is why a GUID can look different after a round trip through .NET.
Shell
# Linux, from the kernel
cat /proc/sys/kernel/random/uuid
# uuidgen, from util-linux
uuidgen --random # v4
uuidgen --time # v1
# Python one-liner, portable
python3 -c "import uuid; print(uuid.uuid4())" Questions
Is UUIDv7 ready to use in production?
Yes. RFC 9562 has been final since May 2024, and PostgreSQL 18, .NET 9 and the major Go, Java, Python and Rust libraries all implement it. The specification is stable and the format will not change.
How much faster is v7 than v4 as a primary key?
It depends entirely on your engine, table size and insert rate, so treat any single number you see quoted with suspicion. The mechanism is not in doubt: random keys scatter inserts across the index and cause page splits, sequential keys do not. The effect is negligible on a small table and pronounced on a large one under sustained insert load.
Can I convert a v7 UUID back to a timestamp?
Yes, and this page does it for you. The first 48 bits are milliseconds since the Unix epoch, big-endian, so the first twelve hexadecimal digits parsed as an integer give the creation time directly.
Does UUIDv7 need a database extension in PostgreSQL?
Not from PostgreSQL 18, which has a native uuidv7() function. On earlier versions you either generate them in the application or install an extension such as pg_uuidv7.
What happens when two machines generate v7 UUIDs at the same instant?
They will share the timestamp prefix and differ in the 74 random bits, so they will not collide but their relative order within that millisecond is arbitrary. The monotonic counter only orders values from a single generator. If you need a global total order, a UUID of any version is the wrong tool.