miniature-id
v1.0.0
Published
A tiny, dependency-free library for generating short, readable, configurable IDs using cryptographically secure randomness.
Maintainers
Readme
miniature-id
A tiny, dependency-free library for generating short, readable, configurable IDs using cryptographically secure randomness.
Not a UUID replacement. Not a NanoID clone. A simple, ergonomic ID generator.
import { tinyId } from "miniature-id";
tinyId(); // "kA9xmP"Formerly published as
tiny-id. Same library, same API, new name.
v1.0.0 is a breaking change: minimum supported runtime is now Node.js 24+ (previously 18+). If you're on an older Node version, stay on
0.1.0instead. See Runtime Support for why.
Why miniature-id?
Most projects don't need a UUID's 36 characters or a full spec implementation — they need a short, unique-enough, URL-safe string for a user ID, session token, or database key. miniature-id does exactly that, plus a few genuinely useful extras, and nothing else:
- Tiny. Core is ~1.4 KB minified + brotli; the optional
words()/emoji()extras live in a separate subpath so they never cost you anything unless you import them. - Zero dependencies. Nothing to audit, nothing to break.
- Secure by default. Always uses
crypto.getRandomValues— neverMath.random(). - Correct. Rejection sampling avoids the modulo bias that a naive
byte % alphabet.lengthimplementation would introduce. - Cross-runtime. Node.js, Bun, Deno, Cloudflare Workers, Vercel Edge, browsers, Electron.
- Small, typed API. One core function, five presets, batch generation, and two optional "fun" generators.
If you need collision-probability guarantees, parsing/validation, or a specific ID spec (UUID, ULID, CUID), those are explicitly out of scope here — see FAQ.
Installation
npm install miniature-idQuick Start
import { tinyId } from "miniature-id";
tinyId(); // "kA9xmP"
tinyId({ length: 12 }); // "aK93Pm2LxQ8r"
tinyId({ prefix: "usr_" }); // "usr_jK92Px"
tinyId({ alphabet: "ABC123" }); // "A1C2B3"
tinyId({ human: true }); // avoids 0/O, 1/I/l, 5/S, 8/BAPI
tinyId(options?)
Generates a random ID as a string.
tinyId(options?: TinyIdOptions): stringOptions
| Option | Type | Default | Description |
| ---------- | --------- | -------------- | ---------------------------------------------------------------------------------------- |
| length | number | 6 | Number of characters (not counting prefix/suffix). Must be a non-negative integer. |
| alphabet | string | A-Z a-z 0-9 | Custom character set. Duplicate characters are ignored. Overrides human. |
| prefix | string | "" | Prepended to the result. |
| suffix | string | "" | Appended to the result. |
| human | boolean | false | Omits visually confusing characters. Ignored if alphabet is set. |
interface TinyIdOptions {
length?: number;
alphabet?: string;
prefix?: string;
suffix?: string;
human?: boolean;
}Human mode removes characters that are easily confused with one another in many fonts:
| Removed | Because it looks like |
| ------------- | ------------------------------ |
| 0, O | zero / letter O |
| 1, I, l | one / capital I / lowercase L |
| 5, S | five / letter S |
| 8, B | eight / letter B |
tinyId({ human: true }); // "ktxrGqPn"Presets
import { short, medium, long, hex, numeric } from "miniature-id";
short(); // 6 chars, A-Z a-z 0-9 — e.g. "R8ikRY"
medium(); // 12 chars, A-Z a-z 0-9 — e.g. "gPN68gLjox5Z"
long(); // 21 chars, A-Z a-z 0-9 — e.g. "tLDYEZajRQ6rjHskxOERL"
hex(); // 12 hex chars — e.g. "971bacf8e561"
numeric(); // 8 digits — e.g. "63689229"Every preset accepts the same options as tinyId() to override its
defaults — e.g. hex({ length: 8, prefix: "req_" }).
Presets (and batch, below) are also attached to tinyId for convenience:
tinyId.short();
tinyId.hex();
tinyId.batch(5);Prefer the named exports for smaller bundles. import { hex } from
"miniature-id" lets a bundler drop everything else you don't use;
tinyId.hex() pulls in the whole tinyId object and all attached methods,
since they're properties on it.
batch(count, options?)
Generates count IDs in one call.
import { batch } from "miniature-id";
batch(5); // ["kA9xmP", "Zq3rTb", "aK93Pm", "xR2vLn", "Qw81Zk"]
batch(3, { prefix: "usr_", length: 8 });
batch(100, { unique: false }); // skip the uniqueness guarantee, for speedBy default every ID in the result is unique — collisions are retried
automatically. If count is impossible for the given alphabet/length (e.g.
batch(1000, { alphabet: "AB", length: 1 }), where only 2 unique IDs can
ever exist), batch() throws immediately with a clear message instead of
retrying forever.
interface BatchOptions extends TinyIdOptions {
unique?: boolean; // default: true
}miniature-id/fun — optional extras
Two playful generators, kept in a separate subpath so they don't add a single byte to the core bundle unless you actually import them:
import { words, emoji } from "miniature-id/fun";
words(); // "brave-otter-42"
words({ separator: "_", numberLength: 0 }); // "swift_falcon"
emoji(); // "🦊🌙🍄"
emoji({ length: 6 });words() draws from a small, hand-curated list (64 adjectives x 64 nouns)
plus an optional trailing number for extra uniqueness — good for things like
container names, feature branches, or temporary environment labels. It is
not meant for high-volume or security-sensitive IDs: the keyspace is
small (~410,000 combinations with the default 2-digit number) compared to
tinyId()'s alphanumeric output. Use a longer numberLength, or switch to
tinyId()/a preset, if that matters for your case.
emoji() draws from a curated set of 32 neutral emoji (animals, plants,
simple symbols — no faces, gestures, or flags) — fun for avatars, reaction
IDs, or party codes, but not recommended anywhere the ID needs to be typed,
sorted, or stored in a system with shaky emoji support.
interface WordsOptions {
separator?: string; // default: "-"
numberLength?: number; // default: 2 (0 disables the trailing number)
prefix?: string;
suffix?: string;
}
interface EmojiOptions {
length?: number; // default: 3
prefix?: string;
suffix?: string;
}Examples
// A user ID
tinyId({ prefix: "usr_", length: 10 }); // "usr_kA9xmPQz3r"
// A short-lived token
tinyId({ length: 24 });
// A human-readable invite code (no confusing characters)
tinyId({ human: true, length: 8 }).toUpperCase(); // e.g. "KTXRGQPN"
// A request ID
hex({ prefix: "req_" }); // "req_971bacf8e561"
// A batch of unique coupon codes
batch(50, { human: true, length: 10 });
// A friendly container name
words(); // "calm-heron-19"Runtime Support
| Runtime | Supported | Notes |
| ------------------- | :-------: | -------------------------------------------------------------------------- |
| Node.js 24+ | ✅ | Uses globalThis.crypto.getRandomValues natively — no fallback needed. |
| Bun | ✅ | |
| Deno | ✅ | |
| Cloudflare Workers | ✅ | |
| Vercel Edge | ✅ | |
| Browsers | ✅ | Any browser with window.crypto.getRandomValues (all modern browsers). |
| Electron | ✅ | |
Node.js 24 is the minimum supported version as of v1.0.0. Every runtime
above exposes globalThis.crypto.getRandomValues natively, so there's a
single, simple code path with no fallback logic and no environment-specific
workarounds required.
Upgrading from 0.1.0? That version additionally supported Node.js 18–23, via a
node:cryptofallback for environments withoutglobalThis.crypto. That fallback (and the runtimes it existed for) has been removed entirely in v1.0.0 — if you're on Node.js 18–23, stay on0.1.0, or upgrade Node.
TypeScript
miniature-id is written in TypeScript and ships its own types — no
@types package needed.
import { tinyId, type TinyIdOptions } from "miniature-id";
const opts: TinyIdOptions = { length: 10, human: true };
tinyId(opts);FAQ
How long should my ID be?
Longer IDs have a lower collision probability. As a rough guide: the
built-in long() preset (21 characters over a 62-character alphabet) is
the same length nanoid defaults to, chosen for very low collision odds at
high volumes. If you need a precise collision-probability calculation for
your exact alphabet/length/volume, that's outside this library's scope —
plenty of birthday-problem calculators exist online.
Why not use UUIDs? UUIDs are a great, standardized choice, especially if you need interoperability with systems that expect that specific format. miniature-id exists for cases where you want something shorter and more readable, and don't need the UUID spec itself.
Does miniature-id validate or parse IDs it generates?
No — that's explicitly out of scope. miniature-id only generates strings;
it doesn't validate format, decode metadata, or guarantee cross-call
uniqueness (except within a single batch() call, when unique: true).
Can I use miniature-id for security tokens (e.g. password reset links)?
miniature-id always uses a cryptographically secure random source, so the
randomness itself is appropriate for that use case. Whether the length
and alphabet you choose give enough entropy for your threat model is a
judgment call you should make deliberately — longer is safer. Stick to
tinyId()/the numeric presets for this; words()/emoji() have a much
smaller keyspace and are not intended for anything security-sensitive.
Why is tinyId.hex() bigger than import { hex }?
See Presets above — it's a deliberate tree-shaking trade-off,
not a bug.
What happened to tiny-id?
This library was previously published as tiny-id. It's been renamed to
miniature-id (the tiny-id name is no longer maintained under this
project). The API is unchanged aside from the new additions documented
here.
Security Notes
- miniature-id only uses cryptographically secure randomness
(
globalThis.crypto.getRandomValues) — neverMath.random(). - Character selection uses rejection sampling, not
byte % alphabet.length, to avoid modulo bias (some characters being subtly more likely than others). This matters most for alphabet sizes that aren't a power of two — which is most custom alphabets, including the default 62-character one. The same rejection-sampling guarantee applies towords()/emoji()'s list selection. - This library makes no collision-probability guarantees, except within a
single
batch()call whenunique: true(the default). Longer IDs and larger alphabets reduce collision risk; you're responsible for choosing parameters appropriate to your use case and expected volume. - This library does not implement any specific security-token spec (e.g. CSRF tokens, session IDs) — it generates random strings, which you can use as a building block for those.
Bundle Size & Performance
Minified + brotli:
| Import | Size (approx.) |
| ------------------------------------------------- | ---------------: |
| import { tinyId } from "miniature-id" | ~1.4 KB |
| Everything from the core (tinyId + all presets + batch) | ~1.4 KB |
| import { words, emoji } from "miniature-id/fun" | ~1.7 KB |
Rough throughput comparison (ops/sec, single machine, not a rigorous
benchmark — see benchmark/benchmark.mjs):
| Library | ops/sec (approx.) |
| --------------------------------- | ------------------: |
| tinyId() (6 chars) | ~4,200,000 |
| tinyId({ length: 21 }) | ~1,500,000 |
| uuid (uuidv4()) | ~4,850,000 |
| crypto.randomUUID() | ~5,600,000 |
| nanoid() | ~22,000,000 |
miniature-id is in the same ballpark as uuid/crypto.randomUUID(), and
slower than nanoid, which uses a more aggressively optimized internal
pooling strategy. For the vast majority of applications (generating IDs in
response to user actions, not in a tight hot loop), this difference is not
meaningful — but we'd rather show the real numbers than claim to be
"faster" without evidence.
License
MIT
