-
Notifications
You must be signed in to change notification settings - Fork 2
ID CUID2
Cryptographically secure, collision-resistant identifier — configurable length, lowercase-alphanumeric, not time-sortable by design.
- Overview
- Format Structure
- API Reference
- Usage Examples
- Why CUID2 Is Not Time-Sortable
- Choosing a Length
- See Also
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.
k 3rj9xn8q1p7m2w5y6h4t8d9
│ └─────────────────────┘
│ 23
│ letter + alphanumeric body (default length = 24)
| Component | Length | Description |
|---|---|---|
| Lead | 1 | Random lowercase letter [a-z]
|
| Body | length - 1 |
Random base36 chars [a-z0-9]
|
| Total | 24..32 | Full identifier |
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).
Generate a CUID2 identifier.
function cuid2(length?: number): string;| Parameter | Type | Default | Description |
|---|---|---|---|
length |
number |
24 |
Total length. Must be an integer in 24..32. |
string — A length-character CUID2 ([a-z][a-z0-9]{length-1}).
InvalidOptionError — If length is not an integer in 24..32.
import { cuid2 } from '@tundralibs/id';
const id = cuid2(); // 24 chars
const long = cuid2(32); // 32 charsimport { cuid2 } from '@tundralibs/id';
const userId = cuid2(); // e.g. "k3rj9xn8q1p7m2w5y6h4t8d9"
const orderId = cuid2(28); // e.g. "k3rj9xn8q1p7m2w5y6h4t8d9a2b3"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);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);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).
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).
- Main ID Documentation — Overview of all ID generators
- CUID v1 — Process-sortable predecessor
- ULID — Distributed-safe sortable identifiers
- Comparison Guide — Choosing the right ID type
- Original CUID2 Spec — Reference implementation