NanoID Generator

Generate short, URL-safe, cryptographically random identifiers with whatever length and alphabet you need. The sampling is unbiased — mask and reject, never modulo — so every symbol in your alphabet is exactly as likely as every other. The calculator below tells you how long a given configuration lasts before a collision becomes plausible.

Collision calculator — how long until a duplicate is likely?

Characters come from crypto.getRandomValues. Bytes that would fall outside a whole number of alphabet cycles are discarded and redrawn rather than folded back with %, which is the difference between a uniform identifier and one whose first few symbols are over-represented.

Press Generate to create identifiers.
Your generated NanoIDs will appear here.

What a NanoID is

A NanoID is the simplest identifier design there is: pick an alphabet, pick a length, and draw that many characters uniformly at random from a cryptographic source. There is no timestamp, no counter, no machine identifier, no version field and no internal structure of any kind. The whole value is entropy.

The default configuration is 21 characters from a 64-symbol alphabet — the uppercase letters, the lowercase letters, the digits, plus _ and -. Sixty-four symbols is exactly six bits each, so a default NanoID carries 126 bits of entropy. That number is not a coincidence: a UUID v4 carries 122 random bits in 36 characters, so 21 characters buys you slightly more unpredictability in 15 fewer characters. The choice of _ and - as the two non-alphanumeric symbols is the same choice base64url makes, and for the same reason — both survive a URL path, a query string, a filename and a DOM id without escaping.

That is the entire appeal. Where a UUID is a standard with a binary layout, a version registry and decades of database support, a NanoID is a convention you can reimplement in ten lines. The trade is deliberate: you give up the ecosystem and gain the ability to say "seven characters, uppercase and digits only, because this goes on a printed voucher".

Why modulo is wrong, and what this page does instead

This is the part that separates a correct generator from one that merely looks correct, and it is worth following carefully because the broken version produces output that passes every casual inspection.

Suppose you want characters from a 62-symbol alphabet and you have a source of random bytes. The obvious approach is alphabet[byte % 62]. A byte holds 256 equally likely values. Dividing 256 by 62 gives 4 with a remainder of 8. So values 0–247 distribute evenly — four bytes map to each of the 62 symbols — but the leftover values 248–255 map to symbols 0 through 7 a fifth time. Those eight symbols therefore appear with probability 5/256 while the other fifty-four appear with probability 4/256. The first eight characters of your alphabet are 25% more likely than the rest.

Nothing about the output looks wrong. Every identifier is still unpredictable in casual terms; you cannot spot the skew by reading a few of them. But the entropy is genuinely lower than the arithmetic suggests, the distribution is a fingerprint of the implementation, and in the pathological case — an alphabet size just over a power of two, say 129 symbols — the bias becomes enormous, with the first symbols appearing nearly twice as often as the last.

The fix is rejection sampling, sometimes called mask-and-reject. Instead of folding out-of-range values back into the alphabet, discard them and draw again:

  • Compute the smallest bit mask that covers the alphabet size. For 62 symbols that is 0b111111 = 63; for 10 symbols it is 0b1111 = 15.
  • Take a random byte and apply the mask. The result is uniform over 0 … mask.
  • If the result is less than the alphabet size, use it. Otherwise throw it away and take the next byte.

Every accepted value is now equally likely, because every accepted value came from the same uniform range and no value was mapped to twice. The cost is that some bytes are wasted — for a 10-symbol alphabet, six of every sixteen masked values are rejected, so you draw about 1.6 bytes per character. That cost is invisible at any realistic scale, and it is the only way to be uniform when the alphabet size is not a power of two.

Two implementation details this page gets right and naive versions often do not. The random pool is requested in one crypto.getRandomValues call sized generously for the expected rejection rate, rather than one call per character, because per-character calls are dramatically slower. And when the pool is exhausted mid-identifier, a fresh pool is drawn and generation continues — the loop is driven by "have I got enough characters yet", never by "have I used all my bytes", which is the bug that produces short identifiers under an unlucky draw.

One happy special case: when the alphabet size is a power of two, the mask covers exactly the alphabet and nothing is ever rejected. The 64-symbol default is such a case, which is why byte & 63 is safe there — and why testing a generator only against the default alphabet will never reveal a modulo bug. Switch this page to the digits-only preset if you want to exercise the rejection path.

Reading the collision calculator

Collisions in a random identifier space follow the birthday problem, not intuition. The relevant number is not "how many possible values are there" but "how many values before two of them coincide", and the second is roughly the square root of the first. A space of 1018 values does not survive 1018 draws — it starts colliding around 109.

The calculator uses the standard approximation for the number of draws n giving collision probability p over a space of N values: n ≈ √(2N·ln(1/(1−p))). It reports the count for a 1% chance and for a 50% chance, then converts both into elapsed time at the generation rate you enter. All of it is computed in logarithms, because N for a default NanoID is about 1037 and would otherwise overflow a double long before the square root.

What the output tends to teach:

  • The default is absurdly safe. 126 bits at a million identifiers per second stays below a 1% collision chance for longer than the universe has existed. Anyone worrying about NanoID collisions at the default settings is worrying about the wrong thing.
  • Short identifiers fail much sooner than people expect. Eight alphanumeric characters is about 47.6 bits — roughly 1014 possibilities — and the first collision becomes likely in the low millions. For a URL shortener doing a few thousand links a day that is fine for years; for an event ID at a thousand per second it is a matter of days.
  • Length beats alphabet size. Each extra character multiplies the space by the alphabet size; switching from 62 symbols to 64 multiplies it by 1.03 per character. If you need more headroom, add a character rather than hunting for exotic symbols.
  • A uniqueness constraint changes the risk, not the maths. A unique index turns a silent collision into a failed insert you can retry. That is the difference between a data-corruption incident and a 409 Conflict, and it costs one line of DDL.

The last point deserves emphasis, because it is the one that actually matters in production. Real duplicate identifiers almost never come from exhausting the entropy space. They come from a broken random source: a library falling back to Math.random, a container fleet whose instances all seed identically at boot, an embedded device with no entropy, or code that generates one identifier and caches it across requests. Put a unique constraint on the column and you will find out immediately. Trust the arithmetic and you will find out from a customer.

Choosing an alphabet

The alphabet is where NanoID earns its keep over a fixed-format identifier, and the right choice comes from asking who or what will handle the value.

SituationAlphabetWhy
URLs, filenames, DOM idsURL-safe 64No escaping anywhere, maximum entropy per character
Case-insensitive systemsLowercase + digits (36)Hostnames, some file systems and several databases fold case; a mixed-case ID collapses and collides
Read aloud or typed by handNo look-alikesRemoving 0/O, 1/I/l and similar pairs eliminates the whole class of transcription errors
Printed on paper or a screen photoUppercase + digits, short, hyphen-groupedLegible at low resolution; grouping helps people keep their place
Interop with hex toolingHex (16)Parses as a number, matches existing regexes, at the cost of 2 bits per character
Numeric-only fieldsDigits (10)Fits legacy systems that store identifiers as numbers — but 3.3 bits per character means you need length

Two rules to keep in mind. First, entropy per character is log₂(alphabet size): 6 bits for 64 symbols, ~5.95 for 62, ~5.17 for 36, 4 for hex and ~3.32 for digits. The readout above shows the total for your current settings, and shrinking the alphabet always means lengthening the identifier to stay in the same place. Second, never start an identifier that will be used as a DOM id, a CSS selector or a hostname label with a digit or a hyphen — CSS identifiers cannot begin with a digit, and DNS labels cannot begin or end with a hyphen. If that applies, generate a first character from a letters-only alphabet and the rest from your full set.

NanoID against the alternatives

NanoID competes with UUID v4 for public identifiers and loses to time-ordered schemes for database keys. Being clear about which job you are doing settles the choice quickly.

Against UUID v4: both are pure randomness from a cryptographic source, and at default settings NanoID has marginally more of it. UUID wins on ecosystem — a native 16-byte column type in most databases, a universally recognised text format, validation built into every framework. NanoID wins on length and on flexibility. If the identifier lives in a database column, the 16-byte storage of a UUID is a real argument; if it lives in a URL a user might type, 21 characters against 36 is a real argument the other way. The UUID generator covers the UUID side of that comparison in full, including how random keys behave in a B-tree index.

Against ULID and UUID v7: not really a comparison, because they answer a different question. Those are time-ordered, so they sort by creation and keep index inserts local — and they publish their creation time to anyone holding one. NanoID sorts arbitrarily and reveals nothing. That makes NanoID a bad clustered primary key and a good public identifier, which is exactly the split most schemas end up with: a ULID or UUID v7 internally, a NanoID in the URL.

Against an auto-increment integer: NanoID exists partly to fix the enumeration problem. Sequential IDs let anyone walk /orders/1 through /orders/n, count your customers from a signup ID, and infer your growth rate from two data points. Unguessable identifiers remove the easy path — although an endpoint that returns other people's data to an authenticated user who guesses an ID is broken regardless, and should answer 403 Forbidden rather than relying on the ID being hard to find.

Against a random hex string: a NanoID is the same idea with a denser alphabet. Thirty-two hex characters and 21 NanoID characters carry almost the same entropy — 128 bits against 126 — so the choice is purely about which text form suits the context. Hex is friendlier to grep and to legacy parsers; NanoID is shorter.

Using generated identifiers in API work

  • Public resource identifiers. /documents/V1StGXR8_Z5jdHi6B-myT tells the world nothing about how many documents exist or when this one was made.
  • Share links and invite codes. Length is user-visible here, so the shorter form genuinely matters — but check the collision calculator before shortening below about 12 characters, and remember an unguessable link is still not an access control.
  • Idempotency keys. A fresh identifier per attempt in an Idempotency-Key header lets a server recognise a retry and return the original result. See the HTTP headers guide for where it belongs and the HTTP methods guide for which verbs need the help.
  • Test fixtures. Unique values per run stop tests colliding in a shared environment. Generate a batch here and paste them into a request body on the API tester.
  • Correlation IDs. One value per inbound request, logged everywhere it goes, turns a distributed failure into a single greppable string. If you also want the time, use a ULID instead.
  • Token identifiers. A NanoID works as a JWT jti claim where a denylist keys on it — the JWT generator will build the token around it.

And the standing caveat, which applies to every unguessable identifier: an identifier is not a credential. Values in URLs end up in server logs, proxy logs, browser history, bookmarks and Referer headers, and a "secret" link leaks the moment someone pastes it into a chat that unfurls previews. Authenticate the request, then use the identifier to find the row.

Frequently asked questions

What is a NanoID?

A short random string drawn from a URL-safe alphabet. The default is 21 characters from 64 symbols — letters, digits, underscore and hyphen — giving 126 bits of entropy with no timestamp, counter or internal structure.

Why is 21 characters the default?

Twenty-one characters at six bits each is 126 bits, slightly more than a UUID v4's 122, in fifteen fewer characters. It is a parity point with UUID rather than a round number.

Why does a naive modulo introduce bias?

A byte has 256 values. With a 62-symbol alphabet, 256 ÷ 62 leaves a remainder of 8, so the first eight symbols get one extra byte mapped to them and appear 25% more often. This generator masks and rejects out-of-range draws instead, so every symbol is equally likely.

Is a NanoID as safe as a UUID?

At the defaults it has marginally more entropy — 126 bits against 122 — and both come from crypto.getRandomValues here. The real difference is structural: UUID has version bits and a standard 16-byte binary form, NanoID has neither.

Can I use a custom alphabet?

Yes, any set of two or more distinct characters. Shrinking the alphabet lowers entropy per character, so lengthen the identifier to compensate — the readout above shows the total as you change it.

Are NanoIDs sortable by creation time?

No. They contain no timestamp, so sorting gives an arbitrary order. That is a weakness for a clustered primary key and a strength for a public identifier.

How many can I generate before a collision?

At the defaults, around 1019 for a one percent chance — longer than the universe has existed at a million per second. Short identifiers are a different story, which is what the calculator is for.

Should a NanoID be my primary key?

Usually not the clustered one. Random keys scatter B-tree inserts and NanoID has no compact binary form. A time-ordered internal key with a NanoID as the unique public column gives you both properties.

Is a NanoID a secret?

Unguessable, but not confidential. URLs are logged, bookmarked and forwarded. Authenticate the request; do not let an identifier be the only barrier.

Related tools and reference