nanoid vs ULID vs cuid2: Unique ID Generation in 2026
nanoid generates 61 million IDs per week based on its npm download count — it's the most-used non-UUID ID generator. ULID encodes a millisecond timestamp in the first 10 characters, so IDs sort chronologically in any database. cuid2 uses SHA-3 and a CSPRNG for the highest collision resistance of any JavaScript ID library. UUID v4 is the boring default that everyone knows. Choosing between them is about what matters for your use case: size, sortability, collision safety, or compatibility.
TL;DR
nanoid for URL-safe IDs in browser and Node.js — 61M weekly downloads, 21 characters, fastest generation, URL-friendly. ULID when IDs need to sort chronologically — embed a timestamp prefix so your database can order records without a separate created_at index. cuid2 for the highest collision resistance and unpredictability — uses SHA-3 and cryptographic randomness, harder to enumerate than nanoid. UUID v4 when you need maximum compatibility (databases, APIs, standards that expect UUIDs). For most applications, nanoid is the pragmatic default; use ULID when sort order matters.
Key Takeaways
- nanoid: 61M weekly downloads, 21 chars, 128-bit entropy, URL-safe (no
=,+,/), works in browser - ULID: 26 chars, 48-bit timestamp + 80-bit random, Base32, sortable by creation time
- cuid2: 24 chars default (configurable to 32), SHA-3 + CSPRNG, highest collision resistance
- UUID v4: 36 chars with hyphens (32 hex), universal standard, 122 bits of randomness
- UUIDv7: New standard — like ULID, timestamp-prefix sortable, replaces ULID for many use cases
- Performance: nanoid > UUID > ULID > cuid2 (cuid2's SHA-3 is slowest but still fast)
The ID Generation Landscape
Why not just use Math.random() or Date.now() for IDs? Two problems:
- Collision: Multiple processes generating IDs at the same time could produce the same ID
- Predictability: Sequential IDs (
1, 2, 3...) let users enumerate records — security issue
The alternatives (nanoid, ULID, cuid2, UUID) solve both. The differences are about additional properties: URL safety, sort order, collision resistance, and standardization.
nanoid
Package: nanoid
Weekly downloads: 61M+
GitHub stars: 24K
Creator: Andrey Sitnik
nanoid generates compact, URL-safe random IDs. The default is 21 characters using an alphabet of A-Za-z0-9_- — no characters that need URL encoding.
Installation
npm install nanoid
Basic Usage
import { nanoid } from 'nanoid';
const id = nanoid(); // "V1StGXR8_Z5jdHi6B-myT"
const id2 = nanoid(10); // "IRFa-VaY2b" (custom length)
Custom Alphabet
import { customAlphabet } from 'nanoid';
// Only lowercase + numbers (no confusing chars)
const generateId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 16);
generateId(); // "3g4kd8n2q7x1mj6p"
// Numbers only (for order codes, PIN codes)
const generatePin = customAlphabet('0123456789', 6);
generatePin(); // "482931"
// Custom short IDs
const generateShortId = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 8);
generateShortId(); // "aB3xK9mR"
Browser Support
// nanoid works natively in browsers (uses Web Crypto API)
import { nanoid } from 'nanoid';
// In React component, Vue, etc:
const userId = nanoid();
// No Node.js required — generates cryptographically secure IDs
// in the browser using crypto.getRandomValues()
Collision Probability
nanoid default (21 chars, URL-safe alphabet, 64 symbols):
~2 quintillion IDs before 1% collision probability.
At 1000 IDs/second: 36 billion years to reach 1% collision chance.
In practice: effectively impossible to collide.
Use Cases for nanoid
// Session tokens
const sessionToken = nanoid(32); // 32-char for security
// Short URL IDs
const shortId = nanoid(8); // "V1StGXR8" — short enough for URLs
// Database primary keys (not sortable)
const itemId = nanoid(); // 21-char default
// File names
const uploadId = nanoid(16); // Unique temp file name
nanoid Limitations
- IDs are not time-sortable — you can't tell which was created first without a separate timestamp
- Larger than ULID for the same entropy (21 vs 26 chars, but ULID includes timestamp)
ULID
Package: ulid
Weekly downloads: 2M+
GitHub stars: 3.5K
ULID (Universally Unique Lexicographically Sortable Identifier) embeds a millisecond timestamp in the first 10 characters of the ID. This means ULID IDs sort chronologically in any database without a separate created_at column index.
Installation
npm install ulid
Basic Usage
import { ulid } from 'ulid';
const id1 = ulid(); // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
const id2 = ulid(); // "01ARZ3NDEKTSV4RRFFQ69G5FAW"
// ^^^^^^^^^^ ^ timestamp prefix
// First 10 chars: millisecond timestamp (Crockford Base32)
// ^ monotonically increasing within same ms
// IDs are lexicographically sortable:
[id2, id1].sort() // [id1, id2] — correct chronological order!
Monotonic Generation (Same Millisecond)
import { monotonicFactory } from 'ulid';
// When generating multiple IDs in the same millisecond,
// use monotonic factory to guarantee sort order:
const ulid = monotonicFactory();
const ids = [ulid(), ulid(), ulid()]; // Generated in same ms
ids // Guaranteed to be in insertion order even within same ms
Database Usage
// ULID as primary key — no separate created_at index needed
// (the ID IS the creation time)
// PostgreSQL
CREATE TABLE events (
id TEXT PRIMARY KEY, -- ULID stored as text
data JSONB
);
// Drizzle ORM
import { text, pgTable } from 'drizzle-orm/pg-core';
import { ulid } from 'ulid';
const events = pgTable('events', {
id: text('id').primaryKey().$defaultFn(() => ulid()),
data: jsonb('data'),
});
// Query in chronological order without timestamp column:
const recent = await db.select().from(events).orderBy(events.id);
// ULID string ordering = chronological ordering
ULID Structure
01ARZ3NDEKTSV4RRFFQ69G5FAV
|----------||--------------|
Timestamp Randomness
10 chars 16 chars
48 bits 80 bits
Millisecond Cryptographically
precision random
ULID Limitations
- Reveals creation timestamp (privacy concern if IDs are public)
- Slightly larger than nanoid (26 chars vs 21 chars)
- Not as widely supported as UUID in database drivers
cuid2
Package: @paralleldrive/cuid2
Weekly downloads: 500K+
GitHub stars: 1.5K
Creator: Eric Elliott
cuid2 is the successor to cuid (deprecated). It uses SHA-3 and a CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) for the highest collision resistance and unpredictability of any JavaScript ID library.
Installation
npm install @paralleldrive/cuid2
Basic Usage
import { createId } from '@paralleldrive/cuid2';
const id = createId(); // "clh3n2i330000d9olmzis3rb8"
// 24 chars default, starts with 'c' (cuid prefix)
Custom Length
import { init } from '@paralleldrive/cuid2';
// Create generator with custom length (up to 32)
const createLongId = init({ length: 32 });
const createShortId = init({ length: 16 });
createLongId(); // "clh3n2i330000d9olmzis3rb8clh3n2i3"
createShortId(); // "clh3n2i330000d9"
Why Higher Collision Resistance
// cuid2 mixes multiple entropy sources:
// 1. Fingerprint (machine/session fingerprint)
// 2. Monotonic counter (increments per call)
// 3. CSPRNG random bytes
// 4. SHA-3 hash of all the above
// This means:
// - Harder to enumerate or guess IDs
// - Collision resistance scales with length
// - No detectable patterns in ID sequence
cuid2 Limitations
- Slower than nanoid (SHA-3 computation adds ~10µs per ID)
- Not sortable by time
- Less ecosystem support than nanoid or UUID
UUID
Package: uuid (or Node.js built-in crypto.randomUUID())
Weekly downloads: 100M+
UUID v4 is the universal standard — every database, API, and protocol understands UUID format:
// Node.js 14.17+: built-in, no package needed
const id = crypto.randomUUID();
// "550e8400-e29b-41d4-a716-446655440000"
// Browser: same API
const id = crypto.randomUUID(); // Works in modern browsers
// uuid package (for older environments or more options):
import { v4 as uuidv4, v7 as uuidv7 } from 'uuid';
uuidv4(); // Random UUID
uuidv7(); // Timestamp-sortable UUID (like ULID but UUID format)
UUID v7 — The New Standard
UUID v7 (RFC 9562, 2024) encodes a millisecond timestamp like ULID, but in standard UUID format:
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// "018f4f2d-6b94-7000-a4e4-8e8f5f4e3a2c"
// ^ timestamp prefix
// UUIDv7 replaces ULID for many use cases:
// - Standard UUID format (database compatibility)
// - Time-sortable like ULID
// - Works with UUID-typed database columns
Comparison
| ID Type | Length | Time-sortable | URL-safe | Crypto secure | Weekly downloads |
|---|---|---|---|---|---|
| nanoid | 21 chars | No | Yes | Yes (CSPRNG) | 61M |
| ULID | 26 chars | Yes | Yes | Yes | 2M |
| cuid2 | 24 chars | No | Yes | Highest (SHA-3) | 500K |
| UUID v4 | 36 chars | No | No (has hyphens) | Yes | 100M |
| UUID v7 | 36 chars | Yes | No (has hyphens) | Yes | 100M |
| crypto.randomUUID | 36 chars | No | No | Yes | (built-in) |
When to Use Each
Choose nanoid if:
- URL-safe IDs for public-facing identifiers (short URLs, tokens)
- Browser client-side ID generation
- You want compact IDs with good entropy
- Session tokens, API keys, file names
Choose ULID if:
- Database primary keys where chronological ordering matters
- Event sourcing or time-series data
- You want to avoid a separate
created_atindex for ordering - Audit logs where ID order = event order
Choose cuid2 if:
- Maximum collision resistance is required
- IDs should be as unpredictable as possible
- High-security systems (financial transactions, security tokens)
Choose UUID v4 / crypto.randomUUID() if:
- Maximum compatibility with existing systems
- Database columns typed as UUID (PostgreSQL, MySQL)
- APIs or specs that require UUID format
- You want zero dependencies (
crypto.randomUUID()is built-in)
Choose UUID v7 if:
- You need both UUID format compatibility AND chronological sorting
- Replacing ULID in a codebase that uses UUID-typed columns
Compare ID generation package downloads on PkgPulse.
See the live comparison
View nanoid vs. ulid vs cuid2 on PkgPulse →