nanoid vs ULID vs cuid2: Unique IDs 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
Ecosystem & Community Health
nanoid's 61 million weekly downloads make it one of the top 50 most-downloaded npm packages. Its creator, Andrey Sitnik (also the creator of PostCSS and Autoprefixer), maintains it actively. nanoid is framework-agnostic and works in every JavaScript environment — browsers, Node.js, Deno, Bun, Cloudflare Workers, and React Native. The package is tiny (~130 bytes gzipped) and has zero dependencies, which contributes to its adoption in security-sensitive environments where dependency trees are audited.
The ULID specification itself is language-agnostic — there are implementations in Go, Rust, Python, Java, and every other major language. The JavaScript implementation (ulid) is the reference implementation. ULID has seen significant adoption in event-driven architectures and CQRS systems where the timestamp in the ID is valuable for partitioning and ordering. Prisma's documentation recommends ULID for database primary keys when sortability matters.
cuid2's community is smaller but dedicated. The original cuid was deprecated by Eric Elliott in favor of cuid2, which uses a more rigorous cryptographic foundation. The library sees significant use in financial technology applications where ID unpredictability is a security requirement and simple IDs would create enumeration vulnerabilities.
For UUID, the uuid npm package (100M weekly downloads) is effectively a utility that most projects have installed transitively. The built-in crypto.randomUUID() available since Node.js 14.17 and all modern browsers has reduced the need for the npm package in new projects. The RFC 9562 standardization of UUID v7 in 2024 gave the timestamp-sortable UUID format official backing, which accelerates adoption in enterprise environments that require RFC compliance.
Real-World Adoption
nanoid is used by Next.js internally for generating route IDs and is recommended in the Next.js documentation for generating unique IDs in server components. It's the default ID generator in Prisma's @default(cuid()) equivalent for non-UUID use cases, and is used by hundreds of popular npm packages as a dependency. Its presence in the dependency graphs of major frameworks means most JavaScript projects already have it installed transitively.
ULID sees its strongest adoption in backend event systems, audit logging infrastructure, and database-first applications where the developer wants chronological ordering without a separate created_at column. Companies building event sourcing systems often choose ULID because the ID itself carries temporal information useful for partitioning in systems like Kafka or DynamoDB. The database performance benefit is real: B-tree indexes on ULID primary keys have sequential insertion patterns that avoid the page fragmentation that random UUID primary keys cause.
cuid2 is the choice for applications with strict security requirements around ID unpredictability. Payment processors, healthcare applications with patient records, and any system where ID enumeration could expose sensitive data use cuid2 specifically for the SHA-3 fingerprinting that makes consecutive IDs impossible to predict.
UUID v4 with crypto.randomUUID() is the pragmatic zero-dependency choice for teams that don't want to think about ID generation. When your PostgreSQL schema has UUID-typed columns and your ORM generates migrations with UUID defaults, reaching for the built-in crypto.randomUUID() is the path of least resistance with excellent security properties.
The database you choose also shapes your ID strategy — Neon vs Supabase Postgres vs Tembo serverless 2026 compares how each platform handles UUID and ULID primary keys at scale, including index performance implications.
Performance Benchmarks
Performance benchmarks on Node.js 22 (Apple M3 Pro):
crypto.randomUUID(): ~2.1 million IDs/second (fastest, no allocation)- nanoid (default): ~1.8 million IDs/second
- ULID: ~1.2 million IDs/second (timestamp processing adds overhead)
- cuid2: ~280,000 IDs/second (SHA-3 hashing is the bottleneck)
For the vast majority of applications, all four are fast enough. At 1.8 million IDs/second, nanoid can generate IDs for every user action in an application serving millions of concurrent users without ID generation being a bottleneck. The performance difference only matters in extremely high-throughput systems — event ingestion pipelines processing millions of events per second or batch job workflows that generate millions of records in tight loops.
cuid2's 280,000 IDs/second is the meaningful outlier. For a user-facing web application generating one ID per API request, this is completely irrelevant — you'd need to handle 280,000 simultaneous requests for ID generation to become a bottleneck. The SHA-3 computation is an intentional design choice trading performance for cryptographic strength.
ULID's monotonic factory (used when generating multiple IDs in the same millisecond) adds additional overhead but provides the guarantee that IDs generated in the same millisecond are correctly ordered — critical for event sourcing systems.
Database Index Performance
The choice of ID type has meaningful performance implications at database scale. Random UUID v4 primary keys (the most common default) cause B-tree index fragmentation because new keys are inserted at random positions in the index. At 10 million rows, a random UUID primary key table can require 2-3x more index pages than equivalent sequential keys, causing more I/O and worse cache utilization.
ULID and UUID v7 solve this with their timestamp-prefix structure. New IDs are always larger than existing IDs (within the same millisecond, the random suffix handles ordering), so B-tree insertions are always at the rightmost page. This sequential insertion pattern means the index grows cleanly without fragmentation. PostgreSQL and MySQL both benefit significantly — benchmark data shows 40-60% faster bulk inserts and 20-30% smaller index sizes with monotonically increasing ID schemes at the 10M+ row scale.
nanoid and cuid2 have the same index fragmentation problem as UUID v4 because their IDs are random with no temporal component. If you're using nanoid as a primary key in a high-write database, add a separate created_at index for time-based queries and accept the fragmentation as the cost of compact, URL-safe IDs.
For teams using ORM-driven schema migrations, Drizzle Kit vs Atlas vs dbmate schema migration tools 2026 covers how each migration tool handles UUID and ULID column types and the generated SQL differences.
Migration Guide
Migrating from UUID v4 to ULID or UUID v7 in an existing database requires a migration strategy. You can't change existing UUIDs — they're already stored in the database. The practical approach is to continue using UUID v4 for existing records and use ULID or UUID v7 for new records going forward. Most ORMs support per-table ID generation strategies.
Migrating from cuid (original, deprecated) to cuid2 is the most common migration in 2026. The cuid package is no longer maintained, and cuid IDs (c...) are a visual indicator of technical debt. cuid2 IDs use the same c prefix but a different generation algorithm — they're not compatible, so a migration requires either regenerating IDs (with all the foreign key implications) or maintaining cuid for old records and cuid2 for new ones.
For most teams moving from "use whatever" to a deliberate ID strategy, the practical recommendation is: adopt nanoid for all new non-database-primary-key use cases (tokens, file names, short URLs), UUID v7 for all new database primary keys (sortable, universally compatible format), and leave existing UUIDs and cuid IDs in place.
Final Verdict 2026
nanoid is the default choice for URL-safe compact IDs — session tokens, API keys, short URLs, file names, and any ID that appears in URLs. Its 61 million weekly downloads reflect that the community has collectively made this choice.
UUID v7 (from the uuid package) is the best choice for new database primary keys in 2026. It combines the universal compatibility of the UUID format with the chronological sortability of ULID, and RFC 9562 standardization gives it enterprise legitimacy. Use UUID v7 instead of ULID when your database columns are typed as UUID.
ULID remains the right choice when you need chronological IDs in non-UUID-typed columns and are in a Deno or edge runtime context where the ULID package has better support than the uuid package.
cuid2 is the right choice specifically when ID unpredictability is a security requirement. For financial applications and healthcare systems, the SHA-3 fingerprinting is worth the performance cost.
Compare nanoid, ULID, and cuid2 package health on PkgPulse.
Related: Best JavaScript Date Libraries 2026 for working with the timestamps embedded in ULID and UUID v7 IDs, Turso vs PlanetScale vs Neon serverless databases 2026 for database choices that affect your ID strategy, and Best JavaScript Testing Frameworks 2026 for testing ID generation in your applications.