UUID vs ULID vs Nanoid: Which Unique ID Should You Use?

UUID vs ULID vs Nanoid: Which Unique ID Should You Use?

Random UUID v4, sortable UUID v7 and ULID, or compact customizable Nanoid: a comparison and online generators for primary keys, public IDs and tokens.

23.09.2026
11 min read
Share this article:
uuidulidnanoididentifiersdatabasetools-dev

Random, sortable or compact IDs do different jobs

A plain auto-increment leaks how many rows a table has. It coordinates poorly across several servers writing at once, and it is guessable: ID 1042 implies that ID 1041 exists. That is why apps generate IDs on the client or the server, without a central counter. The words “unique and unpredictable” do not mean the same thing in every format. The UUID generator produces a random UUID v4, a UUID v7 that starts with a timestamp, or the nil UUID for tests. The ULID generator has the same goal as UUID v7, an ID that sorts by creation time, in a shorter Base32 string. The Nanoid generator leaves out the timestamp and spends every character on randomness, so the string is shorter for the same entropy. All three run in the browser and sit together on the developer utilities hub, next to the regex tester and the password generator.

UUID: v4, v7 or nil, with or without hyphens, lowercase or uppercase, plain, braces or URN format, 1 to 50 IDs per list
ULID: 26 Crockford Base32 characters, uppercase by default, a Monotonic option that keeps one list in ascending order, and the timestamp shown under each ID
Nanoid: any size from 2 to 64 (default 21, five presets), URL-safe, alphanumeric, numeric, no-look-alike or custom alphabet, entropy shown in bits
All three generate through the browser's Web Crypto API, with nothing uploaded; without crypto.getRandomValues, ULID and Nanoid show an error instead of a weaker ID
No Generate button: each option change generates a new list, with Copy all, a .txt download and a copy button per row

Picking the right tool for the context

Good fits for the online generator

Prototyping, testing, and visual checks before writing any server-side code.

Prototyping a database schema and comparing v7 keys against v4 keys immediately
Producing test data or fixtures, 10 to 50 IDs at a time, copied or downloaded as .txt
Checking that the ULID generator's Monotonic option keeps one list in ascending order before relying on the same behavior server-side
Recalculating the real entropy of a custom Nanoid (alphabet, size) before locking it into an API spec
Illustrating a team discussion about sortable vs. random without writing a line of code
What belongs server-side or in a library

The online generator prototypes the format; it does not replace production-scale generation.

High-throughput ID generation in production: a server library (uuid, ulid, nanoid on Node) avoids any browser round trip
Security tokens (password reset, session, invite): a dedicated secret, never a reused public identifier
Authorization checks on a resource: an unpredictable ID never replaces a server-side ownership check
Migrating an existing auto-increment to UUID or ULID in production: plan the dual write and the new index before you retire the old counter

Mistakes that get expensive once you are in production

Using a random UUID v4 as an indexed primary key

A primary key filled with UUID v4 values inserts each new row at a random place in the B-tree index. On a busy table, that splits pages and slows writes. The cause is pure randomness, not UUIDs as such. UUID v7 (RFC 9562) puts a millisecond timestamp in its leading 48 bits and fills the rest with random bits. ULID applies the same idea in Base32. New values then land at the end of the index, like an auto-increment, without exposing a usable counter. RFC 9562 notes that the gap can reach an order of magnitude or more. The UUID generator switches from v4 to v7 without changing the field shape (36 characters with hyphens). The ULID generator offers the shorter 26-character alternative.

Defaulting to v4 for a high-write primary key, then discovering index fragmentation in production
Assuming a “random” UUID is safer than a “sortable” one, when a v7 still holds 74 random bits and a ULID 80
Mixing v4 and v7 in the same column and expecting them to compare chronologically
Never checking whether the database or ORM already recommends a sortable ID for indexed keys
Keep v4 for a purely opaque identifier (session, nonce, token). Switch to v7 or ULID once the table is written continuously and insertion order matters for index performance.
Assuming an unpredictable ID replaces an authorization check

Replacing an auto-increment with a UUID, ULID or Nanoid stops plain enumeration (trying ?id=124, then 125…). It does not check anyone's authorization. An ID can leak through a shared link, a log line or a referrer header. It then stays readable if the API does not verify who owns the resource. ULID adds a problem of its own. Its first 10 characters give away the creation time in plain sight. That is harmless for a primary key. For a token meant to stay opaque, it is a weakness. Anyone who roughly knows the creation time narrows the search before attacking the random part.

Dropping a server-side ownership check because the ID is "hard to guess"
Using a ULID as a password-reset or invite token, assuming it is as opaque as a dedicated secret
Publishing ULIDs in public URLs without realizing they reveal the resource's creation time
Counting on monotonic order across several lists or processes, when the Monotonic option only covers the IDs of one list
UUID, ULID, and Nanoid replace a counter, not a session or a secret. For a short-lived token, generate a dedicated value and always check authorization server-side.
Shrinking a Nanoid's alphabet or size without recalculating entropy

Nanoid's collision resistance depends on size and alphabet together. The tool's default, 21 characters from the 64-symbol URL-safe alphabet, carries 126 random bits, against 122 for a UUID v4. Every other setting changes that math. At 8 characters from the same alphabet, the odds of at least one collision reach 1% after about 2,400,000 IDs. The Numeric alphabet (10 symbols) shrinks the space too, since 21 digits give about 70 bits. Nothing warns you, apart from the entropy in bits shown by the info button next to the Size field.

Switching to the Numeric alphabet for an ID that must stay unique at scale, without checking the displayed entropy again
Dropping to 6 to 8 characters for a “shorter” slug without estimating how many IDs you will really create
Pasting a custom alphabet with repeated characters and counting on its pasted length, when the tool drops duplicates before computing the entropy
Confusing the no-look-alike alphabet (meant for a code read aloud) with an alphabet meant for information density
A shortened Nanoid can look fine in testing and still collide once real production volume is reached, because the entropy math was never rechecked.
Mixing format variants inside the same dataset

The format switch of the UUID tool (plain, braces, URN) and the uppercase switch change the text, not the underlying 128 bits. A column that compares raw strings does not know that {550e8400-…} and urn:uuid:550e8400-… name the same UUID unless you normalize first. ULID has the same trap. Crockford Base32 ignores case on decode, but a UNIQUE constraint on a text column often compares case, as PostgreSQL does by default.

Exporting UUIDs in urn:uuid: format, then comparing them as-is to bare UUIDs already stored in the database
Turning on braces for a manual test and forgetting to strip them before import
Storing ULIDs in uppercase when the client generates them and in lowercase when the server recomputes them
Not normalizing case and hyphens before an equality check or a UNIQUE constraint
Pick one storage format (typically lowercase, hyphenated, no prefix) and normalize any external value before comparing it.

Format, size and sort order of the four variants

UUID v4, UUID v7, ULID, Nanoid

Same goal, uniqueness without a central coordinator, in four different shapes.

UUID v4 (RFC 9562): 128 bits, 122 of them random, 36 characters with hyphens, as in 550e8400-e29b-41d4-a716-446655440000. The 4 opening the third group is the version. It does not sort by creation time. It is the most widely supported format, with a dedicated type in PostgreSQL and MariaDB.
UUID v7: the same 36-character shape, but the leading 48 bits hold a millisecond timestamp and the version digit is 7. It sorts by time even as text, and stays a standard UUID with 74 random bits.
ULID: 26 Crockford Base32 characters (e.g. 01HXYZ0123456789ABCDEFGHJK), 48 bits of time then 80 random bits. Same sort behavior as UUID v7, a shorter string, case-insensitive on decode.
Nanoid: any length from 2 to 64 characters (21 by default) and an alphabet of your choice. No embedded timestamp, so a Nanoid does not sort by creation time. It is the most compact of the four for a comparable entropy.
What the FastMinify generators actually expose

The real options on each tool, as they exist today.

uuid-generator: version v4, v7 or nil, hyphens and uppercase on or off, plain, braces or URN format, 1 to 50 IDs per list. URN forces hyphens and lowercase, and nil always gives a single ID. Each v7 row shows its timestamp.
ulid-generator: uppercase by default, a Monotonic option, the decoded timestamp under each ID, 1 to 50 per list. All IDs of one list share the same millisecond. Monotonic adds one to the previous random part instead of drawing a new one.
nanoid-generator: size 2 to 64 (presets 8, 12, 16, 21 and 32), entropy in bits, 1 to 50 per list. The alphabet is URL-safe by default, or alphanumeric, numeric, no-look-alike or custom. A custom alphabet takes up to 256 characters, and duplicates are dropped.
All three run in the browser through Web Crypto and upload nothing. ULID and Nanoid have no weaker fallback: without crypto.getRandomValues, they show an error instead of a list. The UUID generator falls back to Math.random in that case.
When sort order actually changes the outcome

Three concrete situations where the format choice has a measurable effect.

High-write table with an indexed primary key: v4 scatters inserts across the index. A v7 or a ULID appends at the end, like an auto-increment, without exposing a counter.
"Newest first" sorting without a dedicated created_at column: possible directly on the ID for v7 and ULID (lexicographic order equals chronological order), impossible for v4 or Nanoid.
Syncing distributed systems without a central coordinator: all four formats keep IDs unique in practice without a central server, which an auto-increment cannot do.
Shortest public URL at a comparable entropy budget: a 21-character Nanoid (default) beats a UUID's 36 characters and a ULID's 26.

Generate, adjust, copy

Setting up the UUID and ULID generators

On the UUID generator, the list is generated again as soon as you change an option, and there is no separate Generate button. On the ULID generator, the decoded timestamp of each ID appears right under its row.

1

1. Pick the version or the Monotonic option

On UUID, v4 gives pure randomness, v7 an order by time and nil the all-zero UUID, with the count fixed at 1. The v7 IDs of one list usually share a millisecond, so they are not ordered among themselves. On ULID, all IDs of a list share the same millisecond and come out in random order. Turn on Monotonic to get them in strictly ascending order.

2

2. Adjust hyphens, case and format

On UUID, the URN format forces hyphens and lowercase and disables both switches. On ULID, uppercase is on by default, and Crockford Base32 decodes the same in lowercase.

3

3. Set the count (1 to 50), then copy

Copy the whole list at once, download it as .txt, or copy one row at a time with the per-row button.

Set the Nanoid size and alphabet before copying

The Nanoid generator shows the entropy in bits in the tooltip of the info button next to Size. The value follows each change of size or alphabet, so you do not have to work it out by hand.

Size: 2 to 64 characters, with the slider, the number field or the presets 8 / 12 / 16 / 21 / 32
Alphabet: URL-safe by default, with no + / = to escape in a URL. The others are alphanumeric, numeric, no-look-alike (without 0, O, o, I, l and 1) and custom, up to 256 characters
A custom alphabet loses its duplicates and needs at least 2 different characters
1 to 50 IDs per list, Copy all, a .txt download or a copy button on each row
No fallback if the browser lacks crypto.getRandomValues: an error message replaces the list rather than a weaker ID

The same formats server-side, in Node

UUID v4 and v7 in Node

Node provides crypto.randomUUID() for a UUID v4, with no dependency. Since Node 24.16 and 26.1, crypto.randomUUIDv7() also produces a v7. On older versions, the small uuid package handles the sortable variant, as in this example.

Basic example

const crypto = require('node:crypto') // v4 — built into Node, no dependency const id = crypto.randomUUID() // v7 — Node's crypto.randomUUID() only generates v4 const { v7: uuidv7 } = require('uuid') const sortableId = uuidv7()
ULID in Node

Node has no built-in ULID support. The ulid package generates ULIDs and decodes their embedded timestamp.

Basic example

const { ulid, decodeTime } = require('ulid') const id = ulid() // 26 characters, uppercase const createdAtMs = decodeTime(id)
Nanoid in Node

The nanoid package uses the same URL-safe alphabet and default size as the tool (21 characters). Its customAlphabet function takes your own alphabet and size.

Basic example

const { nanoid, customAlphabet } = require('nanoid') const id = nanoid() // 21 characters, default url alphabet const numericId = customAlphabet('0123456789', 12)()
FastMinify uuid-generator / ulid-generator / nanoid-generator

No install, nothing uploaded, up to 50 IDs per list and every format option of the three tools. The limits: no high-throughput generation in the browser, no monotonic order across lists, tabs or processes, and a custom Nanoid alphabet capped at 256 characters. Use the pages to prototype a schema or produce fixtures, and keep uuid, ulid and nanoid on the server in production.

Conclusion

No identifier fits every case. UUID v4 remains the right call for a purely opaque ID with no sort requirement and the widest native support. UUID v7 or ULID take over once insertion order matters for index performance, or for sorting without a dedicated created_at column. Nanoid gives the shortest string for a given entropy, which helps in a URL. None of the three replaces a server-side authorization check. None is meant for high-throughput generation in the browser either, which is the job of the uuid, ulid and nanoid server libraries. If these IDs travel in an API's JSON payload, the guide on optimizing a JSON REST API is a natural next read. The JSON Schema validator then checks the expected shape of an id field.

UUID v4 for a purely opaque ID with no sort requirement
UUID v7 or ULID once insertion order matters for the index or for application-level sorting
Nanoid for the shortest possible string at a given entropy budget, especially in a URL
None of the three replaces a server-side authorization check
High-throughput production generation: a server library (uuid, ulid, nanoid), not the browser generator
Share this article
Share this article: