Skip to content

UUID v7 Generator and Decoder, with ULID and NanoID

A UUID v7 generator and decoder for RFC 9562 IDs, with ULID, NanoID and UUIDv4 to compare, a monotonic bulk mode, and Postgres 18, Drizzle and D1 code.

  • Runs in your browser

Checked

01Tool

Tool

Generate

Format
Case

The 8-4-4-4-12 form Postgres prints.

Uses rand_a as a counter inside each millisecond, as RFC 9562 section 6.2 describes. rand_b stays random.

For backfills, like the shift argument of uuidv7() in Postgres.

Characters

36

Time bits

48

Random bits

74

IDs appear here once the page has loaded.

IDs are made in this tab with crypto.getRandomValues. Nothing is sent or stored, and only the settings go in the address.

Decode

Braces, urn:uuid: and missing hyphens are fine.

Paste a UUID or ULID to read its version, its variant and the time inside it.

Use it in your database

Where it runs
schema.sql
create table items (
  id uuid primary key default uuidv7()
);

select id, uuid_extract_timestamp(id) as created_at
from items;

Needs PostgreSQL 18 or later. uuid_extract_timestamp() reads versions 1 and 7 and returns null for any other, and its time is only as exact as the code that made the ID.

02Guide

How it works

What UUIDv7 is

A UUIDv7 puts a Unix timestamp in milliseconds at the front and random bits behind it, with the version and variant bits in their usual places. RFC 9562 defined it in 2024, and the RFC asks implementations to prefer it over the older time-based versions 1 and 6. Because the time comes first, v7 IDs sort by creation in plain byte order or text order, with no parsing.

That ordering is the point. RFC 9562 notes that random UUIDs have poor index locality: each new v4 key lands at a random place in a B-tree, and the RFC calls the cost to those indexes dramatic. A v7 key lands near the end of the index, the way an auto-increment key does, and any server can still make one without asking the database.

How it compares with UUIDv4, ULID and NanoID

  • UUIDv4 is random apart from its version and variant. It reveals nothing, which makes it the right choice when creation time must stay private, and it scatters writes across an index.
  • ULID has the same shape as UUIDv7, a millisecond time and then randomness, written in Crockford base32. It sorts as text, but it is a community spec rather than an IETF standard, and its bits are not a valid UUIDv7 because the version and variant bits are random too.
  • NanoID is a short random string in a URL-safe alphabet. It carries no time, so it suits public IDs in URLs better than primary keys.

The table further down gives each format's length and bit counts, with a source for each.

UUIDv7 as a primary key in Postgres and D1

PostgreSQL 18 makes v7 a column default with uuidv7(). Its uuid type holds the 128-bit value rather than text, which is what RFC 9562 recommends for databases. On an older Postgres, keep the uuid column and make the ID in your application: the type accepts any version.

D1 is SQLite, which has no UUID type, so each column stores IDs as TEXT or as a BLOB. TEXT reads well in queries and logs, and a BLOB is smaller. Keep one form per column, since SQLite never treats a TEXT value as equal to a BLOB. Within TEXT, keep one letter case too: digits sort below letters in either case, so all-lowercase and all-uppercase IDs each sort by time, but a mix does not.

What a v7 ID gives away

Anyone holding a v7 ID can read when it was made: the first twelve hex digits are the timestamp, and the decoder above prints it. That is harmless for most records, but it tells the world when a user signed up if their ID appears in a URL.

RFC 9562 is plain about security. UUIDs must not be used as security capabilities, and where an identifier plays any part in security, the RFC says to use v4. For a session token, a reset link or an API key, use a value from the secret key generator instead.

Generating v7 in a Worker

Workers have crypto.getRandomValues, so the uuidv7() function in the Worker code runs unchanged. One behaviour matters: as a guard against Spectre, a Worker's clock advances only after I/O, so every ID made between two I/O calls in a request carries the same millisecond. Their random bits keep them unique, but not in order.

When the order inside a request matters, count within the millisecond. RFC 9562 section 6.2 describes three methods, and this page's monotonic mode uses the first. Its counter starts at a random value with its top bit clear, which leaves it room to count, and if it does run out, the timestamp moves a millisecond ahead, which the RFC allows. Postgres's uuidv7() uses the third method and fills rand_a with time finer than a millisecond.

03Rates

The numbers it uses

The four formats side by side
FormatCharactersTimeRandom bitsSource
UUIDv7 Time first, so it sorts by creation3648-bit Unix ms74rfc-editor.org
ULID Time first, in Crockford base322648-bit Unix ms80github.com
NanoID A–Z, a–z, 0–9, _ and -, at its default length21None126github.com
UUIDv4 Random apart from version and variant36None122rfc-editor.org

A 48-bit millisecond time, in UUIDv7 and ULID alike, runs out in the year 10889, as the Postgres docs note.

04Questions

Questions

What is UUID v7?
UUIDv7 is the time-ordered UUID version that RFC 9562 defined in May 2024. It starts with a Unix timestamp in milliseconds and fills the rest with random bits, so IDs made later sort after earlier ones and any machine can make them without coordination. It fits every column and library that accepts a UUID.
Should I use UUIDv7 or UUIDv4 for primary keys?
UUIDv7, in most cases. RFC 9562 notes that random UUIDs such as v4 have poor index locality, because each insert lands at a random place in the B-tree, while a v7 key lands near the end the way an auto-increment key does. Pick v4 when an ID must not reveal when it was made.
UUIDv7 or ULID: which should I use?
Both put a millisecond Unix time in front of random bits, in 128 bits. UUIDv7 is an IETF standard that fits the Postgres uuid type and every UUID library, while ULID is shorter as text and sorts as a string. Choose v7 when the column or the library expects a UUID, and ULID when the text form matters more.
Does Postgres support UUIDv7?
Yes, from PostgreSQL 18, which added uuidv7() beside gen_random_uuid() and a new uuidv4() alias. uuidv7() takes an optional interval that shifts its timestamp, and uuid_extract_timestamp() reads the time back out. On PostgreSQL 17 or earlier, make the ID in your application and store it in a uuid column.
How do I generate UUIDv7 IDs for Cloudflare D1?
In your Worker. D1 runs SQLite, whose built-in functions include no UUID generator, and D1's docs list none among its extensions. Copy the uuidv7() function from the Drizzle, D1 code above and hand it to Drizzle's $defaultFn, or call it before each insert.
Can you get the creation time from a UUIDv7?
Yes. The first twelve hex digits are the Unix time in milliseconds, so anyone holding the ID can read when it was made, and the decoder above prints it. Don't use v7 where that time is private, and never as a secret.

05Sources

Sources

Nuxter

Building a SaaS on Nuxt?

Nuxter already stamps each analytics session with a UUIDv7, and its Drizzle schema runs on Postgres through Cloudflare Hyperdrive.

See what Nuxter includesFrom $249, one-time.