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:
uuid
ulid
nanoid
identifiers
database
tools-dev

Random, sortable, or compact: three different jobs for one string

A plain auto-increment leaks how many rows a table has, coordinates poorly across multiple servers writing at once, and is guessable — ID 1042 implies an ID 1041 exists. That is why apps generate IDs client-side or server-side without a central counter. But "unique and unpredictable" does not mean the same thing across formats. The UUID generator produces a purely random UUID v4, a UUID v7 whose leading bits encode a timestamp (so it sorts), or the nil UUID for testing. The ULID generator targets the same goal as UUID v7 — an ID that sorts by creation time — with a shorter Base32 string. The Nanoid generator drops the embedded timestamp entirely to optimize string length for a given entropy budget. All three run fully in the browser and sit together on the developer utilities hub, next to the regex tester and the password generator.

UUID: v4 (random), v7 (sortable, 48-bit timestamp), or nil version; hyphens, uppercase, and format (plain, braces, urn:uuid:) are adjustable; 1 to 50 IDs per batch
ULID: 26 Crockford Base32 characters, uppercase by default, a monotonic mode for guaranteed ordering within the same batch, and a decoded timestamp shown under each generated ID
Nanoid: size adjustable from 2 to 64 characters (default 21, presets 8/12/16/21/32), url / alphanumeric / numeric / no-look-alike / custom alphabet up to 256 characters, live entropy display
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: the list updates instantly on every option change, with copy-all, .txt download, or per-row copy

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 once, copied or downloaded as .txt
Visually checking that the ULID generator's monotonic mode keeps ascending order within one batch before relying on it 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 cutting over 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 every new row at a random position in the B-tree index, fragmenting pages and slowing writes on a high-volume table. That is not a UUID problem in general — it is a pure-randomness problem: UUID v7 (RFC 9562) encodes a millisecond timestamp in its leading 48 bits and fills the rest with randomness, and ULID does the same idea in Base32. Both append to the end of the index, like an auto-increment, without exposing a usable counter. The UUID generator switches from v4 to v7 without changing the field shape (36 hyphenated characters); 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 inherently safer than a "sortable" one — the random portion of a v7 or a ULID is still large (74 to 80 bits); only the insertion order changes
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
Assuming an unpredictable ID replaces an authorization check

Replacing an auto-increment with a UUID, ULID, or Nanoid stops trivial enumeration (trying ?id=124, then 125…) but checks nobody's authorization: an ID obtained through another channel — a shared link, a log line, a referrer header — is still readable if the API does not verify ownership of the resource. ULID adds a specific problem: its first 10 characters encode the creation timestamp in plain sight. That is harmless for a primary key, but not for a token meant to stay opaque — anyone who roughly knows the creation time narrows the search space before even attacking the random portion.

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
Never testing the ULID generator's monotonic mode before relying on it server-side to guarantee ordering within the same millisecond
Shrinking a Nanoid's alphabet or size without recalculating entropy

Nanoid's collision resistance depends entirely on size × alphabet. The tool's default (21 characters over the 64-symbol url alphabet) targets a collision probability comparable to a UUID v4 at a realistic generation rate — but every preset changes that math. Switching to the "numeric" alphabet (10 symbols) or dropping to 6-8 characters for a shorter slug can collapse the ID space by several orders of magnitude, with nothing flagging it except the live entropy counter next to the size field.

Switching to the numeric alphabet for an ID that must stay unique at scale, without rechecking the displayed entropy
Dropping to 6-8 characters for a "shorter" slug without estimating the real generation rate
Pasting a custom alphabet with repeated characters and expecting the displayed length — duplicates are deduplicated automatically
Confusing the no-look-alike alphabet (meant for a code read aloud) with an alphabet meant for information density
Mixing format variants inside the same dataset

The UUID tool's format switch (plain, braces, urn:uuid:) and case toggle change the text representation, not the underlying 128 bits — but a column doing plain string comparison does not know that {550e8400-…} and urn:uuid:550e8400-… name the same UUID without normalization first. Same logic for ULID: Crockford Base32 is defined as case-insensitive on decode, but a string UNIQUE constraint is case-sensitive.

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 uppercase from one path (generated client-side) and lowercase from another (recomputed server-side)
Not normalizing case and hyphens before an equality check or a UNIQUE constraint

Format, size, sort order: what actually separates the four

UUID v4, UUID v7, ULID, Nanoid

Same goal — uniqueness without a central coordinator — four different shapes.

UUID v4: 128 bits, 122 of them random, 36 hyphenated characters (e.g. 550e8400-e29b-41d4-a716-446655440000). Not sortable by creation time. The most widely supported format, native to nearly every database (RFC 9562).
UUID v7: same 36-character shape, but the leading 48 bits encode a millisecond timestamp — sortable lexicographically and chronologically while remaining a standard UUID.
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: adjustable length from 2 to 64 characters (default 21), adjustable alphabet. No embedded timestamp — a Nanoid does not sort by creation time. The most compact of the four at a comparable entropy budget.
What the FastMinify generators actually expose

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

uuid-generator: v4 / v7 / nil versions, hyphens on/off, uppercase on/off, plain / braces / urn:uuid: format (urn forces hyphens on and uppercase off), 1 to 50 IDs per batch (forced to 1 for nil).
ulid-generator: uppercase by default, monotonic mode (increments the random component instead of drawing a fresh one within the same batch), decoded timestamp shown under each generated ID, 1 to 50 per batch.
nanoid-generator: size 2-64 with 8/12/16/21/32 presets, url (default) / alphanumeric / numeric / no-look-alike / custom alphabet (up to 256 characters, auto-deduplicated), live entropy display, 1 to 50 per batch.
All three run entirely in the browser through Web Crypto, with nothing uploaded; ULID and Nanoid have no insecure fallback — without crypto.getRandomValues, the tool shows an error instead of a weaker ID.
When sortability actually changes the outcome

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

High-write table with an indexed primary key: v4 fragments the index through random insertions; v7 or ULID append to the end of the index, 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 guarantee uniqueness without a central server — exactly what 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

UUID and ULID: pick the version, then adjust the format

On the UUID generator, the list updates as soon as you change an option — there is no separate "generate" button. On the ULID generator, the decoded timestamp of each ID appears right under the generated row.

1

Step 1 — Pick the version or the sort option

UUID: v4 for pure randomness, v7 for sortability, nil for the all-zero test UUID (the count is then forced to 1). ULID: turn on monotonic mode if you generate several IDs within the same millisecond and need a guaranteed order.

2

Step 2 — Adjust hyphens, case, and format

On UUID, picking the urn:uuid: format automatically forces hyphens on and disables the case toggle. On ULID, uppercase is on by default; Crockford Base32 decodes correctly even in lowercase.

3

Step 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.

Nanoid: set size and alphabet before copying

The Nanoid generator shows live entropy in bits the moment you change the size or alphabet, so you never have to calculate it by hand.

Size: 2 to 64 characters, quick presets 8 / 12 / 16 / 21 / 32
Alphabet: url (default, no + / = so it drops straight into a URL), alphanumeric, numeric, no-look-alike (excludes 0/O and 1/l/I), or custom up to 256 characters
A custom alphabet is auto-deduplicated and needs at least 2 distinct characters to generate
1 to 50 IDs per batch, copy-all, .txt download, or per-row copy
No fallback if the browser lacks crypto.getRandomValues — an error message replaces the list instead of producing a weaker ID

The same formats server-side, in Node

UUID v4 and v7 in Node

Node exposes crypto.randomUUID() natively for a UUID v4, no dependency needed. Node's built-in crypto module does not generate v7 — a small npm package like uuid handles the sortable variant.

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 exposes generation plus decoding of the 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 matches the tool's default url alphabet and default size (21 characters), with customAlphabet for a custom alphabet or 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 batch, full format options for all three formats. Trade-off: no high-throughput generation in the browser, no monotonicity guarantee across tabs or processes, and a custom Nanoid alphabet capped at 256 characters. Use the pages to prototype a schema or produce fixtures; keep uuid, ulid, and nanoid server-side for production.

Conclusion

There is no single "best" identifier. 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 an application-level sort without a dedicated created_at column. Nanoid targets the shortest possible string at a given entropy budget, especially in a URL. None of the three replaces a server-side authorization check, and none is meant for high-throughput generation in the browser — that is what the uuid, ulid, and nanoid server libraries are for. If these IDs travel inside an API's JSON payload, the guide on optimizing a JSON REST API pairs well with this one, and so does the JSON Schema validator for checking 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: