Skip to main content

nanoid vs ULID vs cuid2: Unique ID Generation in 2026

·PkgPulse Team

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:

  1. Collision: Multiple processes generating IDs at the same time could produce the same ID
  2. 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 TypeLengthTime-sortableURL-safeCrypto secureWeekly downloads
nanoid21 charsNoYesYes (CSPRNG)61M
ULID26 charsYesYesYes2M
cuid224 charsNoYesHighest (SHA-3)500K
UUID v436 charsNoNo (has hyphens)Yes100M
UUID v736 charsYesNo (has hyphens)Yes100M
crypto.randomUUID36 charsNoNoYes(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_at index 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.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.