Skip to main content

nanoid vs uuid in 2026: ID Generation Compared

·PkgPulse Team
0

TL;DR

nanoid for compact IDs in modern apps; uuid for standard UUIDs required by databases or external systems. nanoid (~19M weekly downloads) generates 21-character URL-safe IDs — shorter and faster. uuid (~40M downloads) generates RFC 4122 compliant UUIDs — required by PostgreSQL UUID columns, external APIs, and standards-based systems. Both are cryptographically secure.

Key Takeaways

  • uuid: ~40M weekly downloads — nanoid: ~19M (npm, March 2026)
  • nanoid IDs are shorter — 21 chars vs 36 chars (UUID with dashes)
  • Both are cryptographically secure — use CSPRNG internally
  • uuid is standard-compliant — required when RFC 4122 matters
  • nanoid is faster — by a small but measurable margin

ID Generation Philosophy

The choice between nanoid and uuid is fundamentally a question of what you're optimizing for. UUID is a standard (RFC 4122) that has been widely implemented across programming languages and databases for decades. When you use a UUID, you're using an ID format that every database, every API, and every developer recognizes. The cost is verbosity: 550e8400-e29b-41d4-a716-446655440000 is 36 characters including dashes, or 32 characters without.

nanoid takes a different approach: generate the smallest ID that provides the required security guarantees for your specific use case. A 21-character nanoid using a 64-character alphabet gives you 126 bits of entropy — more than UUID v4's 122 bits — in a shorter, URL-safe string. The result is a compact, random ID that works in URLs without encoding, in CSS class names, in filenames, and anywhere else you'd use an identifier.

The practical implication: if you're building a system where IDs will appear in URLs (resource IDs, share links, short codes), nanoid's shorter format provides a meaningfully better user experience. If you're building a system that integrates with external services, uses PostgreSQL's native UUID column type, or needs to interoperate with other codebases, UUID's standardization is worth the verbosity.


Output Comparison

import { v4 as uuidv4 } from 'uuid';
import { nanoid } from 'nanoid';

// UUID v4 — standard format
uuidv4(); // '550e8400-e29b-41d4-a716-446655440000' (36 chars)

// nanoid — URL-safe, no dashes
nanoid(); // 'V1StGXR8_Z5jdHi6B-myT' (21 chars)

// nanoid custom size
nanoid(8);  // 'hFP-zZ2s' (8 chars, higher collision probability)
nanoid(32); // 'M3ZVqQc7_Lx8HnW-FsKdAoBpRyNe6uJ' (32 chars, more secure)

// Custom alphabet
import { customAlphabet } from 'nanoid';
const nanoidNumbers = customAlphabet('1234567890', 8);
nanoidNumbers(); // '87412357' (numeric only — for OTP codes etc.)

const nanoidReadable = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10);
nanoidReadable(); // 'xqtmfbzrkv'

Collision Probability

// Both are secure, but nanoid requires understanding the math

// uuid v4: 122 bits of randomness
// Collision probability: effectively zero for practical purposes
// 1 billion UUIDs/second for 1 year = 0.00000006% chance of collision

// nanoid (21 chars, default alphabet of 64 chars):
// 21 × log2(64) = 21 × 6 = 126 bits of randomness
// Slightly MORE entropy than UUID v4

// nanoid (shorter sizes — higher collision risk):
// 8 chars × 6 bits = 48 bits → NOT secure enough for primary keys
// 12 chars × 6 bits = 72 bits → borderline
// 21 chars × 6 bits = 126 bits → production-safe

// Safe sizes: 16+ characters with default alphabet

Database Usage

// PostgreSQL — native UUID column type
// Prisma schema:
model User {
  id        String   @id @default(uuid()) // or cuid()
  email     String   @unique
}

// uuid package works directly
import { v4 as uuidv4 } from 'uuid';
const userId = uuidv4(); // Matches PostgreSQL UUID type

// nanoid with PostgreSQL:
// Store as TEXT — no native nanoid type
model User {
  id        String   @id @default(cuid())
  // OR use @default("") and generate in app code
}

const userId = nanoid(); // Stored as TEXT/VARCHAR

Performance

Benchmark: Generate 1,000,000 IDs

Library    | Time    | Ops/sec
-----------|---------|----------
nanoid     | 380ms   | 2,630,000
uuid v4    | 520ms   | 1,920,000
crypto.randomUUID() | 200ms | 5,000,000 (Node.js 16+ built-in)

Note: For most apps, generating IDs is not a bottleneck.
The built-in crypto.randomUUID() is fastest but UUID format only.

Package Health

Both nanoid and uuid are among the most downloaded packages on npm, making them effectively infrastructure-level dependencies. Their download counts reflect both direct usage and inclusion as dependencies of other popular packages.

PackageWeekly DownloadsBundle SizeLast ReleaseMaintained
uuid~40M~4KB (ESM)Active (2026)Yes
nanoid~19M~130B (core)Active (2026)Yes

nanoid's ~130 byte bundle size is a remarkable achievement — the entire ID generation logic, including the CSPRNG wrapper and alphabet management, compresses to nearly nothing. This makes it genuinely zero-cost to add to a client-side bundle. uuid's ~4KB is also small by any reasonable standard, but the difference is meaningful for libraries that export nanoid as a dependency.

Both packages are well-maintained with regular releases. The uuid package is maintained by the uuidjs organization and has stable, conservative development. nanoid is maintained by Andrey Sitnik (also the creator of PostCSS and Autoprefixer) and receives regular updates.


UUID Versions Explained

UUID is not a single format — it's a family of formats, each with a different generation strategy. Understanding the versions matters because the wrong version can have security or compatibility implications.

UUID v1 is time-based and includes the MAC address of the generating machine. This creates two problems: first, it reveals your server's network interface identifier (a minor privacy/security leak). Second, v1 UUIDs generated close together on the same machine are sequential, which can be either an advantage (database index locality) or a security risk (predictable IDs). V1 is largely deprecated for new applications.

UUID v3 and v5 are namespace-based and deterministic — they hash an input string (a namespace + name) to produce a UUID. Given the same input, you always get the same UUID. This is useful for creating reproducible IDs: a v5 UUID for the URL https://example.com/users/123 will always be the same UUID, enabling deduplication and idempotent operations. V3 uses MD5 (deprecated for security use cases) while v5 uses SHA-1 (still acceptable for namespace UUIDs where cryptographic security isn't required).

UUID v4 is pure random — 122 bits of randomness with 6 bits used for the UUID version and variant markers. This is what most people mean when they say "UUID," and it's what uuidv4() and crypto.randomUUID() generate.

UUID v7 was standardized in 2024 and solves one of UUID v4's database performance problems. V7 is time-ordered — the first 48 bits encode a millisecond Unix timestamp, followed by random bits. This means v7 UUIDs sort chronologically and are sequential within a millisecond window. For databases that use UUID primary keys with B-tree indexes (PostgreSQL, MySQL), sequential IDs insert at the end of the index rather than randomly distributed, which significantly improves insert performance.

import { v7 as uuidv7 } from 'uuid';

// UUID v7 — time-ordered, sortable
const id1 = uuidv7(); // '018f7d20-1c3d-7e8a-b2c4-d5e6f7a8b9c0'
// Wait 1ms...
const id2 = uuidv7(); // '018f7d20-1c3e-...' (later timestamp prefix)

// v7 IDs sort chronologically:
[id2, id1].sort() // [id1, id2] — oldest first

For new applications using PostgreSQL UUID primary keys, UUID v7 is increasingly the recommended choice over v4 because it provides both uniqueness and index-friendly sequential ordering.


IDs in URLs vs Databases

The performance characteristics of ID types in database indexes is a concrete, measurable concern for high-traffic applications. B-tree indexes (used by PostgreSQL, MySQL, SQLite) perform best when new records are inserted at or near the end of the index — this is the "sequential write" pattern. Completely random IDs (UUID v4, nanoid) cause random insertions throughout the B-tree, which leads to more page splits and higher write amplification as the table grows.

For applications with millions of rows, this random insertion overhead becomes measurable. The traditional solution was auto-incrementing integer primary keys, which are maximally sequential. The modern solution is UUID v7 or ULID (Universally Unique Lexicographically Sortable Identifier) — both provide uniqueness across distributed systems while maintaining sequential ordering.

ULID is worth knowing as an alternative to both nanoid and standard UUIDs for primary keys:

import { ulid } from 'ulid'; // or 'ulidx' for a modern implementation

ulid(); // '01ARZ3NDEKTSV4RRFFQ69G5FAV'
// 26 characters, Crockford base32 encoding
// First 10 chars: millisecond timestamp (sortable)
// Last 16 chars: random component

// Benefits:
// - Time-ordered (better database performance than UUID v4)
// - URL-safe (Crockford base32: no special characters)
// - Sortable (can be sorted lexicographically = chronological order)
// - 128 bits of information (80-bit timestamp + 48-bit random)

For URL safety, nanoid's default alphabet (A-Za-z0-9_-) is URL-safe — no encoding needed. UUID v4 contains only alphanumeric characters and hyphens, which are also URL-safe in most contexts. Neither requires percent-encoding in URLs.

// URL usage comparison
const uuidId = '550e8400-e29b-41d4-a716-446655440000';
const nanoidId = 'V1StGXR8_Z5jdHi6B-myT';

// Both safe in URLs:
`/posts/${uuidId}`   // /posts/550e8400-e29b-41d4-a716-446655440000 (long)
`/posts/${nanoidId}` // /posts/V1StGXR8_Z5jdHi6B-myT (shorter, cleaner)

Security Considerations

Using predictable or sequential IDs for public-facing resources is a serious security vulnerability. Consider an API endpoint GET /api/invoices/12345 — an attacker can enumerate all invoices by iterating the integer. This class of vulnerability is called Insecure Direct Object Reference (IDOR), and it's in the OWASP Top 10.

Random IDs (UUID v4, nanoid) prevent enumeration attacks by making resource IDs unpredictable. Even if an attacker knows the ID format, they cannot discover other users' resource IDs by guessing patterns.

The entropy requirements vary by use case:

Security context         | Minimum entropy | Recommended
-------------------------|----------------|------------
Public resource IDs      | 64 bits        | nanoid(11) or UUID v4
Authentication tokens    | 128 bits       | nanoid(21) or UUID v4
Session IDs              | 128 bits       | nanoid(21) or crypto.randomUUID()
API keys                 | 128+ bits      | nanoid(32) or larger
OTP codes (short-lived)  | 32-48 bits OK  | nanoid('0-9', 6) with rate limiting
Database primary keys    | 64 bits OK     | integer, UUID v7, or ULID

Note that for short-lived, rate-limited identifiers like OTP codes, lower entropy is acceptable because the attack surface is limited — an attacker has seconds, not years, to guess a 6-digit code, and failed attempts should trigger lockouts. For persistent resource IDs with no rate limiting, 128+ bits of entropy is the safe floor.

// Don't use short nanoids for persistent security-sensitive IDs:
nanoid(6) // 'abc123' — 36 bits entropy — NOT for resource IDs

// Use appropriate entropy for the use case:
import { customAlphabet } from 'nanoid';

// OTP: 6 digits, short-lived, rate-limited
const generateOTP = customAlphabet('0123456789', 6);

// API key: 32 chars, URL-safe, long-lived
const generateApiKey = () => nanoid(32);

// Share link: 21 chars, URL-safe, default entropy
const generateShareId = () => nanoid(); // 21 chars = 126 bits

When to Choose

Choose nanoid when:

  • Custom short IDs for URLs (shorter = better)
  • Database stores IDs as VARCHAR/TEXT (not UUID type)
  • Custom alphabets needed (alphanumeric only, numeric-only for OTPs)
  • Bundle size matters (nanoid is smaller than uuid)
  • Modern app without legacy UUID requirements

Choose uuid when:

  • PostgreSQL UUID column type (native UUID support)
  • External API or service requires RFC 4122 UUID format
  • Interoperability with systems expecting UUID format
  • v1 (time-based), v3, or v5 UUIDs needed for specific use cases
  • uuid is already a dependency (don't add nanoid just to be trendy)

Consider crypto.randomUUID() (built-in) when:

  • Node.js 16+ or modern browser
  • You only need v4 UUIDs
  • Zero dependencies is preferred

Compare nanoid and uuid package health on PkgPulse. For schema validation that complements your ID strategy, see Zod vs TypeBox 2026. Browse all packages in the PkgPulse directory.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.