JavaScript random string generator
Generate one here, or copy the JavaScript below. Each example was run on Node.js 24, and the browser examples run unchanged in any current browser.
More options
The short answer
Use crypto.getRandomValues. It fills a typed array from the operating system cryptographically secure generator, it is available in every current browser, and in Node.js it has been a global without any flag since version 19.
The step people get wrong is turning random bytes into characters. A byte has 256 possible values and an alphanumeric alphabet has 62. Taking byte % 62 makes the first eight characters of the alphabet 1.25 times as likely as the others, because 256 divided by 62 leaves a remainder of 8. The fix is to discard any byte of 248 or above, the largest multiple of 62 that fits in a byte, and use the next one.
This page itself generates the string in the box above with the same approach, entirely in your browser.
| You need | Browser, and Node.js 19+ | Node.js only |
|---|---|---|
| A custom alphabet | getRandomValues with rejection | crypto.randomInt(n) per character |
| A URL-safe token | getRandomValues, then base64url | randomBytes(32).toString('base64url') |
| A hex token | getRandomValues, two hex digits per byte | randomBytes(16).toString('hex') |
| A UUID | crypto.randomUUID() | crypto.randomUUID() |
crypto.getRandomValues refuses a request for more than 65,536 bytes and throws QuotaExceededError, which was confirmed on Node.js 24. Code that builds long strings has to fill the array in chunks, as the first example below does.
Why Math.random().toString(36) is not a random string
Math.random().toString(36).substring(2) is short, needs nothing imported, and appears in a great many codebases. It has three separate problems.
The length is not fixed. Across 20,000 calls on Node.js 24 it produced strings from 8 to 14 characters long, most often 11, because the number of base-36 digits depends on the value drawn.
The strength is capped. A double carries 53 bits of precision, so no string made this way holds more than 53 bits however long it looks.
The source is predictable. The ECMAScript specification leaves the algorithm behind Math.random to the implementation and does not require it to be cryptographically secure. V8, the engine in Chrome and Node.js, uses xorshift128+, whose state can be recovered from a small number of observed outputs.
Browser or Node.js
In a browser the value is created on the visitor device and never crosses the network unless your code sends it. That makes the browser the right place for a secret the user keeps, and the wrong place for anything your server has to trust, because a server cannot know how a client produced a value.
In Node.js, the node:crypto helpers save you the rejection loop. crypto.randomInt(n) is unbiased as long as the range is below 2^48, and it throws ERR_OUT_OF_RANGE rather than degrading if you ask for more. crypto.randomBytes with the base64url encoding gives a URL-safe token in one line.
In browsers, crypto.randomUUID is only available in secure contexts, meaning HTTPS pages and localhost. crypto.getRandomValues has no such restriction.
Mistakes that pass code review
Each of these works in a demo, which is how it reaches production.
- Math.floor(Math.random() * chars.length) in a loop. Close enough to uniform, but predictable, so it is fine for test data and wrong for a token.
- bytes[i] % chars.length. A secure source with a biased mapping. With 62 characters, the first eight are 25 percent more likely than the rest.
- Array.from(bytes, b => b.toString(16)).join(''). Bytes below 16 become a single hex digit, so the string comes out shorter than expected. Pad each byte to two digits with padStart(2, '0').
- btoa(String.fromCharCode(...bytes)) in a URL. Standard base64 uses plus, slash and equals. Swap them for hyphen and underscore and strip the padding, or use base64url in Node.js.
- Indexing an alphabet that contains emoji or other characters outside the Basic Multilingual Plane. alphabet[i] returns half of a surrogate pair. Convert the alphabet with Array.from first, and index the array.
- Creating a password reset code or session token in the browser for the server to accept. Anything the server relies on has to be generated on the server.
Unique IDs in bulk
Add strings to a Set until it reaches the size you need. At 16 characters from 62 a collision is so unlikely that the loop almost never repeats, and the Set makes uniqueness certain rather than probable.
If the IDs also need to sort by creation time, a random string is the wrong format. A UUID version 7 or a ULID carries a timestamp at the front and sorts correctly as text.
Doing this in your own code
JavaScript - browser, and Node.js 19+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
function randomString(length, alphabet = ALPHABET) {
const n = alphabet.length;
if (n < 2 || n > 256) throw new RangeError("alphabet must have 2 to 256 characters");
// Bytes at or above this limit are thrown away. It is the largest
// multiple of n that fits in a byte, so no character is favoured.
const limit = 256 - (256 % n);
let out = "";
while (out.length < length) {
// getRandomValues refuses more than 65,536 bytes per call.
const bytes = crypto.getRandomValues(new Uint8Array(Math.min(65536, (length - out.length) * 2)));
for (const b of bytes) {
if (b < limit) out += alphabet[b % n];
if (out.length === length) break;
}
}
return out;
}
console.log(randomString(24));
console.log(randomString(24));
console.log(randomString(24)); The alphabet must be single UTF-16 code units. For emoji, index an array made with Array.from(alphabet).
Node.js 15.7+ - node:crypto helpers
import { randomBytes, randomInt, randomUUID } from "node:crypto";
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// randomInt rejects out-of-range draws internally, so this is unbiased.
const custom = Array.from({ length: 24 }, () => ALPHABET[randomInt(ALPHABET.length)]).join("");
console.log(custom); // 24 characters
console.log(randomBytes(32).toString("base64url")); // 43 characters, 256 bits
console.log(randomBytes(16).toString("hex")); // 32 characters, 128 bits
console.log(randomUUID()); // 36 characters, 122 random bits JavaScript - password with every character type
const CLASSES = ["ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz", "0123456789", "!#$%*+-=?@^_"];
const ALPHABET = CLASSES.join("");
function randomIndex(n) {
// Rejection sampling over a 32-bit draw, uniform for any n up to 2^32.
const limit = 2 ** 32 - (2 ** 32 % n);
const buf = new Uint32Array(1);
do crypto.getRandomValues(buf); while (buf[0] >= limit);
return buf[0] % n;
}
function password(length = 20) {
if (length < CLASSES.length) throw new RangeError("too short to include every class");
// Whole-string rejection keeps the result uniform over valid passwords.
for (;;) {
let s = "";
for (let i = 0; i < length; i++) s += ALPHABET[randomIndex(ALPHABET.length)];
if (CLASSES.every((cls) => [...s].some((c) => cls.includes(c)))) return s;
}
}
for (let i = 0; i < 5; i++) console.log(password(20)); JavaScript - many unique strings
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
function randomIndex(n) {
const limit = 2 ** 32 - (2 ** 32 % n);
const buf = new Uint32Array(1);
do crypto.getRandomValues(buf); while (buf[0] >= limit);
return buf[0] % n;
}
function randomString(length) {
let s = "";
for (let i = 0; i < length; i++) s += ALPHABET[randomIndex(ALPHABET.length)];
return s;
}
const ids = new Set();
while (ids.size < 1000) ids.add(randomString(16));
console.log([...ids].join("\n")); Questions
How do I generate a random string in JavaScript?
Fill a Uint8Array with crypto.getRandomValues, skip any byte at or above 256 - (256 % alphabet.length), and map the rest with byte % alphabet.length. That is secure and unbiased, and it works in browsers and in Node.js 19 and later without imports.
Is Math.random secure for generating tokens?
No. The specification does not require it to be cryptographically secure, and in V8 it is xorshift128+, whose internal state can be recovered from a few outputs. Use crypto.getRandomValues in the browser, or crypto.randomBytes and crypto.randomInt in Node.js.
How do I generate a random string in Node.js?
For a token, crypto.randomBytes(32).toString('base64url') gives 43 URL-safe characters. For a chosen alphabet, pick each character with crypto.randomInt(alphabet.length), which handles the rejection sampling for you. Both come from node:crypto.
Why is my random hex string shorter than expected?
Because b.toString(16) turns any byte below 16 into a single digit, so the leading zero is lost. Use b.toString(16).padStart(2, '0') for each byte. In Node.js, randomBytes(n).toString('hex') always returns exactly 2n characters.
Can I use crypto.randomUUID() as a random string?
Yes for uniqueness, with caveats. It returns 36 characters with 122 random bits, but four characters are hyphens and one digit is always 4, so it is longer than it needs to be. For a compact token, base64url-encode 16 or 32 random bytes instead.
What is the longest string crypto.getRandomValues can produce in one call?
It fills at most 65,536 bytes per call and throws QuotaExceededError above that. For longer output, call it repeatedly in chunks and concatenate the results.