Last Updated: 3/9/2026
Custom Alphabets
Design custom alphabets for specific use cases while maintaining security and usability.
Why Custom Alphabets?
Default alphabet (A-Za-z0-9_-):
- URL-safe
- 64 characters
- Case-sensitive
Custom alphabets for:
- Human-readable codes (no ambiguous characters)
- Specific formats (hex, numeric, etc.)
- Database constraints (case-insensitive)
- Industry standards (Base32, etc.)
Alphabet Design Principles
1. Remove Ambiguous Characters
For human-readable IDs, remove characters that look similar:
import { customAlphabet } from 'nanoid'
// Remove: 0/O, 1/I/l
const nanoid = customAlphabet('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', 8)
console.log(nanoid()) //=> "3KJRT8XY"
// Easy to read and typeRemoved characters:
0(zero) vsO(letter O)1(one) vsI(letter i) vsl(lowercase L)- Sometimes:
2/Z,5/S,8/B
2. Choose Appropriate Size
Smaller alphabets need longer IDs for same security:
| Alphabet Size | ID Length | Entropy | Use Case |
|---|---|---|---|
| 10 (digits) | 15 | 50 bits | Numeric codes |
| 16 (hex) | 16 | 64 bits | Hex identifiers |
| 32 (base32) | 20 | 100 bits | Human-readable |
| 36 (alphanumeric) | 14 | 73 bits | Case-insensitive |
| 64 (default) | 21 | 126 bits | URL-safe |
Formula: entropy = log2(alphabet_size) × length
3. Consider Case Sensitivity
Case-sensitive (52 letters + 10 digits):
const nanoid = customAlphabet(
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
12
)Case-insensitive (26 letters + 10 digits):
const nanoid = customAlphabet(
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
14
)4. Avoid Special Characters
Unless necessary, avoid characters that need URL encoding:
// ❌ Bad: Needs URL encoding
const bad = customAlphabet('!@#$%^&*()', 10)
// ✅ Good: URL-safe
const good = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 10)Common Alphabets
Digits Only
import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('0123456789', 12)
console.log(nanoid()) //=> "849201736584"Use cases:
- Order numbers
- Invoice IDs
- Tracking codes
Hexadecimal
import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('0123456789abcdef', 16)
console.log(nanoid()) //=> "4f90d13a42b8c7e1"Use cases:
- Color codes
- Hash-like identifiers
- Technical IDs
Base32 (RFC 4648)
import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', 16)
console.log(nanoid()) //=> "3KJRT8XY2MN4PQ7Z"Use cases:
- Human-readable codes
- Case-insensitive systems
- QR codes
Alphanumeric (No Ambiguous)
import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('346789ABCDEFGHJKLMNPQRTUVWXY', 8)
console.log(nanoid()) //=> "6RTUVWXY"Use cases:
- Support ticket IDs
- Voucher codes
- Product codes
Lowercase Only
import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 12)
console.log(nanoid()) //=> "k3xm9tn2pq7z"Use cases:
- URL slugs
- Domain names
- Email addresses
Using nanoid-dictionary
Pre-made alphabets from nanoid-dictionary:
npm install nanoid-dictionaryimport { customAlphabet } from 'nanoid'
import { lowercase, numbers, nolookalikes } from 'nanoid-dictionary'
// Lowercase only
const lowerNanoid = customAlphabet(lowercase, 12)
// Numbers only
const numericNanoid = customAlphabet(numbers, 10)
// No lookalike characters
const readableNanoid = customAlphabet(nolookalikes, 8)Available alphabets:
lowercase- a-zuppercase- A-Znumbers- 0-9alphanumeric- A-Za-z0-9nolookalikes- Removes 0/O, 1/I/l, etc.nolookalikesSafe- Even more removedhex- 0-9a-f
Security Considerations
Maximum Alphabet Size
⚠️ Must be ≤256 characters:
// ✅ OK: 64 characters
const ok = customAlphabet('A-Za-z0-9_-', 21)
// ❌ INSECURE: >256 characters breaks security
const bad = customAlphabet('...300 characters...', 21)Why: Internal algorithm uses Uint8Array (0-255 values).
Collision Probability
Smaller alphabets = higher collision risk:
import { customAlphabet } from 'nanoid'
// 10 chars, 10 digits = 10^10 = 10 billion combinations
const numeric = customAlphabet('0123456789', 10)
// For 1 million IDs: ~5% collision probability ⚠️Always check: https://zelark.github.io/nano-id-cc/
Power-of-2 Alphabets
Best performance with power-of-2 sizes:
// ✅ Fast: 64 = 2^6 (no rejection sampling)
const fast = customAlphabet(
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
21
)
// ⚠️ Slower: 30 chars (rejection sampling needed)
const slow = customAlphabet('0123456789ABCDEFGHIJKLMNOPQR', 21)Power-of-2 sizes: 2, 4, 8, 16, 32, 64, 128, 256
Testing Your Alphabet
Collision Test
import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('0123456789', 8)
const ids = new Set()
const count = 100000
for (let i = 0; i < count; i++) {
ids.add(nanoid())
}
const collisions = count - ids.size
console.log(`Collisions: ${collisions} out of ${count}`)
console.log(`Collision rate: ${(collisions / count * 100).toFixed(2)}%`)Readability Test
import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', 8)
// Generate sample IDs
for (let i = 0; i < 10; i++) {
console.log(nanoid())
}
// Check for:
// - Ambiguous characters (0/O, 1/I/l)
// - Profanity (use nanoid-good package)
// - Easy to type
// - Easy to read aloudBest Practices
✅ Do’s
Test collision probability:
// Use calculator: https://zelark.github.io/nano-id-cc/
// Input: alphabet size, ID length, expected countDocument alphabet choice:
// Using Base32 for human-readable support ticket IDs
// No ambiguous characters (0/O, 1/I/l removed)
const ticketId = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', 8)Reuse generator:
// ✅ Create once
const nanoid = customAlphabet('0123456789', 10)
// Use many times
for (let i = 0; i < 1000; i++) {
const id = nanoid()
}❌ Don’ts
Don’t use >256 characters:
// ❌ Breaks security guarantees
const bad = customAlphabet('...300 chars...', 21)Don’t ignore collision probability:
// ❌ High collision risk not checked
const risky = customAlphabet('01', 10) // Only 1024 combinations!Don’t recreate generator:
// ❌ Slow: Creates generator every time
for (let i = 0; i < 1000; i++) {
const id = customAlphabet('0123456789', 10)()
}See Also
- customAlphabet() API
- Collision Probability
- nanoid-dictionary
- nanoid-good - Profanity filter