Skip to content

ID CUID2

GitHub Actions edited this page Aug 24, 2026 · 6 revisions

CUID2

Cryptographically secure, collision-resistant identifier — configurable length, lowercase-alphanumeric, not time-sortable by design.

Deno Bun Node.js Cloudflare Workers Browser License

Table of Contents

Overview

CUID2 is the cryptographically secure successor to CUID. Every character is drawn from crypto.getRandomValues, the format deliberately omits a timestamp segment (so the minting time can't be reconstructed), and the length is configurable to match your collision- resistance budget.

  • Cryptographically secure: full body sourced from crypto.getRandomValues.
  • No information leakage: no embedded timestamp, counter, or machine fingerprint.
  • Configurable length: 24..32 chars (default 24). Longer = lower collision probability.
  • URL- and shell-safe: lowercase letters + digits only.

Pairs with Guardian.string().cuid2({ length? }) from @tundralibs/guardian, which validates the same [a-z][a-z0-9]{length-1} format.

Format Structure

k 3rj9xn8q1p7m2w5y6h4t8d9
│ └─────────────────────┘
│            23
│  letter + alphanumeric body  (default length = 24)

Component Breakdown

Component Length Description
Lead 1 Random lowercase letter [a-z]
Body length - 1 Random base36 chars [a-z0-9]
Total 24..32 Full identifier

Character Set

CUID2 uses lowercase base36: 0-9 followed by a-z. The leading character is restricted to letters (so the ID never looks like a number to downstream parsers).

API Reference

cuid2()

Generate a CUID2 identifier.

function cuid2(length?: number): string;

Parameters

Parameter Type Default Description
length number 24 Total length. Must be an integer in 24..32.

Returns

string — A length-character CUID2 ([a-z][a-z0-9]{length-1}).

Throws

InvalidOptionError — If length is not an integer in 24..32.

Example

import { cuid2 } from '@tundralibs/id';

const id = cuid2(); // 24 chars
const long = cuid2(32); // 32 chars

Usage Examples

Basic Usage

import { cuid2 } from '@tundralibs/id';

const userId = cuid2(); // e.g. "k3rj9xn8q1p7m2w5y6h4t8d9"
const orderId = cuid2(28); // e.g. "k3rj9xn8q1p7m2w5y6h4t8d9a2b3"

Sensitive Contexts (Public-Facing IDs)

CUID2 is appropriate when the ID is shown to users or attackers, because it leaks no information:

import { cuid2 } from '@tundralibs/id';

// Password reset tokens, email verification tokens, magic links —
// anything an attacker might try to enumerate or backdate.
const resetToken = cuid2(32);
const verificationCode = cuid2(28);

Validation Pairing

import { cuid2 } from '@tundralibs/id';
// Needs a separate install: deno add @tundralibs/guardian
import { Guardian } from '@tundralibs/guardian';

const Cuid2Guard = Guardian.string().cuid2({ length: 24 });

const id = cuid2();
const ok = Cuid2Guard.parse(id);

Why CUID2 Is Not Time-Sortable

CUID v1 (and ULID) encode the minting timestamp into the ID's high-order characters, giving free sortability — but at the cost of leaking when the ID was created. For database primary keys that never leave the server, this is usually a non-issue. For tokens, public URLs, or anything an attacker can see, the timestamp leak can:

  • Reveal user signup patterns / activity windows.
  • Make brute-force enumeration cheaper (attacker can narrow the search to recent IDs).
  • Correlate seemingly-independent events that happened at the same time.

CUID2 trades sortability for privacy. If you need sortability AND a privacy-preserving format, you can pair cuid2() with a separate sortable column (e.g. createdAt).

Choosing a Length

Collision probability scales with the size of the random body. The defaults are chosen to make collisions vanishingly unlikely even at billions of generations.

Length Random bits (approx) Collision after N IDs (50% probability)
24 ~119 bits ~10¹⁸ — fine for almost any application
28 ~140 bits ~10²¹
32 ~160 bits ~10²⁴ — overkill for nearly anything

When in doubt: use the default (24).

See Also


← Back to ID Documentation

Clone this wiki locally