Generating random strings comes up constantly in backend work — session tokens, invoice numbers, API keys, temporary codes, test data. Most of the time you end up copy-pasting the same Math.random().toString(36) snippet or reaching for a heavy library when you only need one function.
I built genkode to fix that for myself, and after using it across a few projects I cleaned it up and published it. Zero dependencies, full TypeScript support, and a small focused API that handles the cases I kept running into.
Here's what it does and how I built it.
Installation
npm install genkode
The basics
The main export is generateCode. At its simplest:
import { generateCode } from 'genkode';
generateCode({ length: 12 });
// → 'rqfvYxJRWfoP' (alpha by default)
generateCode({ length: 12, type: "alphanumeric" });
// → 'aZ8kL2pQ9xW1'
generateCode({ length: 12, type: "numeric" });
// → '362128126198'
Three types: alpha, numeric, alphanumeric. That covers most cases without any configuration overhead.
Cryptographically secure mode
By default the library uses Math.random — fast, but not suitable for anything security-sensitive. Set secure: true and it switches to crypto.getRandomValues from the Web Crypto API:
generateCode({ length: 32, type: "alphanumeric", secure: true });
// → 'Tz3mW8qA1nXpKj7rBv2cYs9dLf4mNe6h'
There's an important subtlety here though: naively doing byte % charsetLength introduces modulo bias — characters at the start of your charset get picked slightly more often than characters at the end when 256 doesn't divide evenly by your charset size.
genkode uses rejection sampling to eliminate this. Any random byte that would cause bias is discarded and a new one is fetched. The result is a perfectly uniform distribution across the charset.
// Internal logic (simplified)
const threshold = Math.floor(256 / max) * max;
if (byte < threshold) {
result.push(charset[byte % max]);
}
// Otherwise discard and try again
Performance: buffered generation
Both standard and secure modes use buffered byte generation rather than calling crypto.getRandomValues once per character. The buffer is sized to the request (capped at 65,536 bytes, which is Node's hard limit for a single getRandomValues call) and refilled in chunks as needed.
For most use cases this is invisible, but for high-volume batch generation it makes a meaningful difference.
Prefix and suffix
A pattern I kept needing: codes with a static prefix that identify their type at a glance — INV- for invoices, USR- for users, TKN- for tokens.
generateCode({ length: 8, prefix: "INV-" });
// → 'INV-rqfvYxJR'
generateCode({ length: 8, prefix: "ORD-", suffix: "-2025" });
// → 'ORD-rqfvYxJR-2025'
The length always refers to the generated segment — not the total string length including affixes. So INV- + 8 chars = 12 characters total.
Batch generation
Pass count and you get an array back instead of a single string. The return type is overloaded in TypeScript so you get correct inference automatically:
generateCode({ length: 10, count: 5 });
// → ['rqfvYxJRWf', 'TzAmW8qAnX', 'pLkQmNbVcD', 'HgFsJwKyRt', 'MnPxZuCvBe']
Add unique: true and the library guarantees no duplicates in the batch. If you ask for more unique codes than the charset and length combination can produce, it throws a KodeError upfront rather than looping forever:
generateCode({ length: 10, count: 1000, unique: true });
// → string[] with 1000 guaranteed-unique codes
generateCode({ length: 1, type: "numeric", count: 11, unique: true });
// → throws KodeError: cannot generate 11 unique codes from charset of size 10
Short ID mode
The shortId flag switches to a reduced charset that strips visually ambiguous characters — 0, O, 1, l, I. These are the characters that cause support tickets when users misread a code from a screen or printout.
generateCode({ length: 12, shortId: true });
// → '3Ks9mBx4nPqR' — no 0/O/1/l/I
The charset is 23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ — 54 characters, still high entropy, fully URL-safe.
Case control
Sometimes you need all uppercase (licence keys, voucher codes) or all lowercase (slugs, usernames). The case option handles this at the charset level rather than post-processing:
generateCode({ length: 12, case: "upper" });
// → 'RQFVYXJRWFOP'
generateCode({ length: 12, case: "lower" });
// → 'rqfvyxjrwfop'
generateCode({ length: 12, type: "alphanumeric", case: "upper" });
// → 'AZ8KL2PQ9XW1'
It composes cleanly with everything else:
// Human-readable voucher code: no ambiguous chars, all caps
generateCode({ length: 8, shortId: true, case: "upper", prefix: "SALE-" });
// → 'SALE-3KS9MBXN'
Error handling
Rather than silently producing garbage for invalid inputs, genkode throws a typed KodeError:
import { generateCode, KodeError } from 'genkode';
try {
generateCode({ length: 0 });
} catch (err) {
if (err instanceof KodeError) {
console.error(err.message);
// → 'length must be at least 1, got: 0'
}
}
It validates that length and count are integers, that length is between 1 and 100,000, and that unique batch requests are actually satisfiable.
Quick reference
| Option | Type | Default | Description |
|---|---|---|---|
length |
number |
— | Required. Length of the generated segment (1–100,000) |
type |
"alpha" |
"numeric" |
"alphanumeric" |
case |
"upper" |
"lower" |
— |
secure |
boolean |
false |
Cryptographically secure output |
prefix |
string |
"" |
Static prefix |
suffix |
string |
"" |
Static suffix |
count |
number |
— | Batch size — returns string[]
|
unique |
boolean |
false |
Guarantee uniqueness within batch |
shortId |
boolean |
false |
Exclude ambiguous characters |
Real-world examples
// API key
generateCode({ length: 32, type: "alphanumeric", secure: true, prefix: "sk_live_" });
// → 'sk_live_Tz3mW8qA1nXpKj7rBv2cYs9dLf4m'
// Invoice number
generateCode({ length: 8, type: "numeric", prefix: "INV-" });
// → 'INV-36212812'
// Voucher code (human-readable, no ambiguous chars)
generateCode({ length: 8, shortId: true, case: "upper", prefix: "SALE-" });
// → 'SALE-3KS9MBXN'
// Seed 100 unique test user IDs
generateCode({ length: 12, count: 100, unique: true, prefix: "user_" });
// → ['user_rqfvYxJRWfoP', ...]
// Short session token
generateCode({ length: 24, type: "alphanumeric", secure: true });
// → 'Tz3mW8qA1nXpKj7rBv2cYs9d'
Links
- npm: npmjs.com/package/genkode
- Related JS-only package: node-mumble
If you find it useful or have a feature in mind, feedback is welcome. Happy to discuss the rejection sampling approach or the buffering strategy in the comments too.
Top comments (0)