
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
A tiny, tree-shakeable ID toolkit for TypeScript apps. Secure core with optional subpath exports for prefixed IDs, typed IDs, validation, and custom alphabets.
A tiny, tree-shakeable ID toolkit for TypeScript apps.
sigilid gives you a secure, URL-safe ID generator as a zero-dependency ESM-first package. The root import is intentionally minimal — extra utilities like prefixed IDs, typed IDs, and validation live in subpath exports so your bundler only pulls in what you actually use.
import { generateId } from "sigilid";
const id = generateId(); // "K7gkJ_q3vR2nL8xH5eM0w"
crypto.getRandomValues, not Math.randomA-Z a-z 0-9 _ -sigilid/native for Node-only throughput tuninginstall sigilid, then import only what you needsigilid/nativeAll sizes are brotli-compressed. Each subpath is a standalone module — importing one never pulls in the others.
| Import | Size |
|---|---|
sigilid | ~297 B |
sigilid/non-secure | ~214 B |
sigilid/prefix | ~385 B |
sigilid/typed | ~398 B |
sigilid/validate | ~360 B |
sigilid/alphabet | ~380 B |
Zero runtime dependencies. Verified by size-limit on every PR.
npm install sigilid
pnpm add sigilid
yarn add sigilid
Node 20+ required. Works in all modern runtimes that expose the Web Crypto API (globalThis.crypto).
Optional native path:
npm install sigilid @sigilid/native-addon
Use @sigilid/native-addon only if you plan to import sigilid/native.
import { generateId } from "sigilid";
// Default: 21 URL-safe characters using crypto.getRandomValues
generateId(); // "K7gkJ_q3vR2nL8xH5eM0w"
generateId(12); // "aX4_p9Qr2mNs"
Most apps eventually need more than a plain random string. They need prefixed IDs to distinguish entity types in logs, branded TypeScript types to prevent mixing userId and postId, and validation helpers at API boundaries.
sigilid is a focused toolkit for exactly that. The root package is as lean as it gets. Everything optional is a subpath import.
| If you need... | Import from... |
|---|---|
| A secure random URL-safe ID | sigilid |
| A non-crypto ID (tests, fixtures) | sigilid/non-secure |
Prefixed IDs like usr_abc123 | sigilid/prefix |
| Branded TypeScript ID types | sigilid/typed |
| Validation at API boundaries | sigilid/validate |
| IDs from a custom character set | sigilid/alphabet |
The root import has no dependency on any of the subpath modules. Importing only sigilid will not pull in prefix, validation, or alphabet code.
sigilid — secure rootimport { generateId, DEFAULT_ALPHABET } from "sigilid";
generateId(); // 21-character secure ID
generateId(12); // 12-character secure ID
console.log(DEFAULT_ALPHABET);
// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
generateId throws a RangeError if length is outside the range 1–255 or is not an integer.
sigilid/non-secure — Math.random-basedimport { generateNonSecureId } from "sigilid/non-secure";
generateNonSecureId(); // 21-character ID using Math.random
generateNonSecureId(8); // 8-character ID
Not suitable for tokens, secrets, or session identifiers. Use this only when you explicitly do not need cryptographic quality — for example, in test fixtures or non-sensitive local keys.
sigilid/native — optional Node-only fast pathimport { generateDefault, generateId } from "sigilid/native";
generateDefault(); // 21-character secure ID
generateDefault(32); // 32-character secure ID
// Alias that mirrors the root naming
generateId(21);
@sigilid/native-addon.sigilid.node-gyp build.sigilid and @sigilid/native-addon are versioned/published separately.If you want the broadest compatibility (browser, edge, and Node) stick to the root
sigilid import.
sigilid/prefix — prefixed IDsimport { generatePrefixedId, createPrefixedGenerator } from "sigilid/prefix";
// One-off prefixed ID
generatePrefixedId("usr"); // "usr_K7gkJ_q3vR2nL8xH5eM0w"
generatePrefixedId("doc", 10); // "doc_aX4p9Qr2mN"
// Factory for repeated use
const userId = createPrefixedGenerator("usr");
userId(); // "usr_K7gkJ_q3vR2nL8xH5eM0w"
userId(); // "usr_Xp9mN2qL5vR8nK3eJ7cHw"
Prefix rules:
_Throws TypeError for invalid prefixes. Throws RangeError for invalid lengths.
sigilid/typed — branded TypeScript ID typesimport { createTypedGenerator, castId } from "sigilid/typed";
import type { IdOf, Brand } from "sigilid/typed";
// Define typed generators for your entities
const userId = createTypedGenerator<"User">("usr");
const postId = createTypedGenerator<"Post">("post");
const uid = userId(); // IdOf<"User"> = "usr_K7gkJ_q3vR2nL8xH5eM0w"
const pid = postId(); // IdOf<"Post">
// TypeScript prevents mixing them up
function getUser(id: IdOf<"User">) {
/* ... */
}
getUser(uid); // ✓
getUser(pid); // ✗ type error
// Cast an untyped string at a trust boundary
const fromDb = castId<"User">(row.user_id);
// Unprefixed typed ID
const tokenGen = createTypedGenerator<"Token">();
const token = tokenGen(); // IdOf<"Token">, no prefix
Brand<T, B> and IdOf<T> are pure type-level utilities — no runtime cost.
sigilid/validate — validation helpersimport { isValidId, assertValidId, parseId } from "sigilid/validate";
import type { ValidationOptions } from "sigilid/validate";
// Boolean check
isValidId("K7gkJ_q3vR2nL8xH5eM0w"); // true
isValidId("bad id!"); // false
isValidId("usr_K7gkJ_q3vR2nL8xH5eM0w", { prefix: "usr" }); // true
isValidId("abc123", { length: 6, alphabet: "abc123def456" }); // true
// Throws TypeError if invalid — good for API boundaries
assertValidId(req.params.id);
assertValidId(req.params.id, { prefix: "usr" });
// Returns the value if valid, throws if not — useful in pipelines
const id = parseId(rawInput);
const id = parseId(rawInput, { prefix: "usr", length: 21 });
ValidationOptions:
| Option | Type | Description |
|---|---|---|
length | number | Expected length of the ID (or ID portion after prefix) |
prefix | string | Expected prefix; separator _ is assumed |
alphabet | string | Characters the ID must be drawn from (defaults to DEFAULT_ALPHABET) |
sigilid/alphabet — custom alphabetsimport { createAlphabet, validateAlphabet } from "sigilid/alphabet";
// Validate first (optional — createAlphabet validates internally)
validateAlphabet("0123456789abcdef");
// Create a bound generator
const hex = createAlphabet("0123456789abcdef");
hex.generate(); // 21-character hex string
hex.generate(32); // 32-character hex string
// Binary IDs (contrived, but works)
const binary = createAlphabet("01");
binary.generate(16); // "1010011001110101"
createAlphabet throws immediately if:
generate(length?) uses rejection sampling to avoid modulo bias.
If you have multiple entity ID types in your codebase, the TypeScript compiler
can silently allow you to pass a userId where a postId is expected — both
are just string. Branded types close that gap.
import { createTypedGenerator } from "sigilid/typed";
import type { IdOf } from "sigilid/typed";
const newUserId = createTypedGenerator<"User">("usr");
const newPostId = createTypedGenerator<"Post">("post");
type UserId = IdOf<"User">;
type PostId = IdOf<"Post">;
// Your service functions now accept precise types
async function deletePost(postId: PostId) {
/* ... */
}
async function getUser(userId: UserId) {
/* ... */
}
const uid = newUserId();
const pid = newPostId();
deletePost(pid); // ✓
deletePost(uid); // ✗ Argument of type 'IdOf<"User">' is not assignable to 'IdOf<"Post">'
import { parseId } from "sigilid/validate";
// In an Express/Hono/Fastify handler
app.get("/users/:id", (req, res) => {
const id = parseId(req.params.id, { prefix: "usr" });
// id is a plain string, validated — throws before reaching your service
});
Because each subpath is a separate bundle with no cross-imports, bundlers like
Vite, esbuild, and webpack can eliminate unused entrypoints entirely. An app
that imports only generateId will not include any prefix, validation, or
alphabet code.
Nano ID is excellent. If all you need is the smallest possible secure random string generator, it may still be the right call — it has a longer track record and an even smaller core.
sigilid is worth considering if:
If you are already using Nano ID and are happy with it, there is no compelling
reason to switch just for the root generateId function — the behavior is
similar. The subpath ecosystem is where sigilid earns its place.
sigilid uses globalThis.crypto.getRandomValues, which is available in:
If you are targeting an environment without Web Crypto, use sigilid/non-secure
with the understanding that Math.random is not cryptographically safe.
sigilid/native is a separate Node-only path. It depends on the companion addon
package and is not intended for browsers or edge runtimes.
For local performance checks, run:
npm ci
npm run build
npm run bench
Native vs JS benchmark:
npm run build:native-addon
npm run bench:native
Build prebuilt binaries for publishing the addon:
npm run prebuild:native-addon
| Import | Entry file | Description |
|---|---|---|
sigilid | dist/index.js | Secure root generator |
sigilid/native | dist/native.js | Optional Node-only native fast path |
sigilid/non-secure | dist/non-secure.js | Math.random-based generator |
sigilid/prefix | dist/prefix.js | Prefixed ID helpers |
sigilid/typed | dist/typed.js | Branded types and typed generators |
sigilid/validate | dist/validate.js | Validation helpers |
sigilid/alphabet | dist/alphabet.js | Custom alphabet factory |
All exports are ESM (.js) with TypeScript declarations (.d.ts). Node.js 20+ required.
Contributions are welcome. See CONTRIBUTING.md for setup instructions, coding standards, and PR expectations.
See ARCHITECTURE.md for an explanation of the design decisions and constraints contributors should keep in mind.
sigilid uses Semantic Versioning. Breaking API changes
will bump the major version. Releases are cut from GitHub — bump the version in
package.json, tag the release, and the publish workflow handles the rest.
MIT — see LICENSE.
FAQs
A tiny, tree-shakeable ID toolkit for TypeScript apps. Secure core with optional subpath exports for prefixed IDs, typed IDs, validation, and custom alphabets.
We found that sigilid demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.