X Xerobit

CUID2 — Collision-Resistant IDs Better Than UUID v4

CUID2 generates secure, URL-safe, database-friendly IDs with better collision resistance than UUID v4. Learn how CUID2 differs from UUID, nanoid, and ULID, with JavaScript and...

Mian Ali Khalid · · 4 min read
Use the tool
UUID Generator
Generate UUID v4 and v7 identifiers in bulk.
Open UUID Generator →

CUID2 is a modern ID format designed to fix UUID v4’s weaknesses: poor collision resistance at scale, no timestamp ordering, and potential fingerprinting. It’s shorter than UUID (24 chars), URL-safe, and starts with a letter.

Generate UUID and CUID identifiers with the UUID Generator.

Install and generate

import { createId } from '@paralleldrive/cuid2';  // npm install @paralleldrive/cuid2

const id = createId();
// "clnfkzj2b0000358s9mf01234"  (24 chars, lowercase alphanumeric)

// Custom length (default 24, min 2, max 32):
import { init } from '@paralleldrive/cuid2';

const cuid = init({ length: 16 });
const shortId = cuid();  // "clnfkzj2b0000358s"

CUID2 vs UUID v4 vs nanoid vs ULID

PropertyUUID v4CUID2nanoidULID
Length36 chars24 chars21 chars26 chars
FormatHex + dashesAlphanumericURL-safeAlphanumeric
SortableNoNoNoYes (by time)
Starts with letterNoYesVariableVariable
URL-safeNo (-)YesYesYes
Timestamp componentNoNoNoYes
Collision resistanceGoodBetterVery goodGood
Fingerprint riskLowNoneNoneLow
// UUID v4 example:
import { v4 as uuid } from 'uuid';
uuid();  // "550e8400-e29b-41d4-a716-446655440000" (36 chars, has dashes)

// CUID2 example:
createId();  // "clnfkzj2b0000358s9mf01234" (24 chars, no dashes)

// nanoid example:
import { nanoid } from 'nanoid';
nanoid();  // "V1StGXR8_Z5jdHi6B-myT" (21 chars, uses URL-safe chars)

// ULID example (time-sortable):
import { ulid } from 'ulid';
ulid();  // "01ARZ3NDEKTSV4RRFFQ69G5FAV" (26 chars, lexicographically sortable)

Why CUID2 is better than UUID v4 at scale

UUID v4: 122 bits of randomness
At 1 billion UUIDs, collision probability ≈ 0.0000000002% (negligible)
At 1 trillion UUIDs, collision probability ≈ 0.00002% (concerning for huge databases)

CUID2: uses SHA-3 hash of entropy pool + counter + timestamp + fingerprint
Better statistical distribution due to hash function
Counter prevents collisions when generating multiple IDs in the same millisecond

For typical apps (< 1 billion records): UUID v4, CUID2, and nanoid are all fine
For large-scale systems: CUID2 or nanoid provide better guarantees

CUID2 structure (how it works)

"clnfkzj2b0000358s9mf01234"
  c          — always starts with a letter (safe for HTML IDs, CSS classes)
  lnfkzj2b   — entropy from timestamp
  0000       — counter (increments for multiple IDs in same ms)
  358s9mf    — fingerprint (process/browser fingerprint hash)
  01234      — additional random entropy
  
Total: 24 chars of [a-z0-9]

The fingerprint makes CUID2 safe against ID collisions even when generated in:

  • Multiple parallel processes
  • Multiple browser tabs
  • Distributed servers

Database usage

// Prisma schema with CUID2:
// schema.prisma
/*
model User {
  id    String @id @default(cuid())  // Prisma uses CUID (v1) — use cuid2 in app code
  name  String
  email String @unique
}
*/

// Better: generate in application code:
import { createId } from '@paralleldrive/cuid2';
import { prisma } from './db';

async function createUser(data) {
  return prisma.user.create({
    data: {
      id: createId(),  // CUID2 generated here
      ...data,
    },
  });
}
-- PostgreSQL: use TEXT or CHAR(24) for CUID2
CREATE TABLE users (
  id    CHAR(24) PRIMARY KEY DEFAULT '',  -- application generates the ID
  name  TEXT NOT NULL,
  email TEXT UNIQUE NOT NULL
);

-- Index performance: CUID2 starts with 'c' + timestamp bytes
-- B-tree index: decent performance, not as optimal as sequential integer IDs
-- For insert-heavy workloads: consider ULID (sorted) or integer IDs

When to use CUID2 vs alternatives

Use UUID v4 when:
  - Standard interoperability is needed (most APIs use UUID)
  - You need RFC 4122 compliance
  - Team is already familiar with UUID

Use CUID2 when:
  - IDs appear in URLs (no dashes, URL-safe)
  - IDs appear in HTML/CSS (starts with letter, valid identifier)
  - High-scale generation across distributed systems
  - Security-conscious: no timestamp component = no info leak

Use ULID when:
  - You need time-ordered IDs for better database index locality
  - You want lexicographically sortable IDs

Use nanoid when:
  - Shortest possible ID (21 chars)
  - Custom alphabet needed
  - Browser bundle size matters (tiny library)

Use integer/serial IDs when:
  - Maximum database performance
  - IDs never exposed to users
  - Monolithic architecture (no distributed generation)

React / Next.js: generate IDs client-side

// For temporary IDs in UI state (list items, form fields):
import { createId } from '@paralleldrive/cuid2';
import { useState } from 'react';

function TodoList() {
  const [todos, setTodos] = useState([]);

  function addTodo(text) {
    setTodos(prev => [...prev, {
      id: createId(),  // Temporary ID until server confirms
      text,
      done: false,
    }]);
  }
  
  return (/* ... */);
}

Related posts

Related tool

UUID Generator

Generate UUID v4 and v7 identifiers in bulk.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.