Skip to Content
advanced-topicsCustom Alphabets

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 type

Removed characters:

  • 0 (zero) vs O (letter O)
  • 1 (one) vs I (letter i) vs l (lowercase L)
  • Sometimes: 2/Z, 5/S, 8/B

2. Choose Appropriate Size

Smaller alphabets need longer IDs for same security:

Alphabet SizeID LengthEntropyUse Case
10 (digits)1550 bitsNumeric codes
16 (hex)1664 bitsHex identifiers
32 (base32)20100 bitsHuman-readable
36 (alphanumeric)1473 bitsCase-insensitive
64 (default)21126 bitsURL-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-dictionary
import { 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-z
  • uppercase - A-Z
  • numbers - 0-9
  • alphanumeric - A-Za-z0-9
  • nolookalikes - Removes 0/O, 1/I/l, etc.
  • nolookalikesSafe - Even more removed
  • hex - 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 aloud

Best Practices

✅ Do’s

Test collision probability:

// Use calculator: https://zelark.github.io/nano-id-cc/ // Input: alphabet size, ID length, expected count

Document 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