npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sigilid

v1.2.0

Published

A tiny, tree-shakeable ID toolkit for TypeScript apps. Secure core with optional subpath exports for prefixed IDs, typed IDs, validation, and custom alphabets.

Downloads

45,853

Readme

sigilid

A tiny, tree-shakeable ID toolkit for TypeScript apps.

CI npm version bundle size install size license

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"

Features

  • Cryptographically secure — uses crypto.getRandomValues, not Math.random
  • URL-safe by default — 64-character alphabet: A-Z a-z 0-9 _ -
  • Tree-shakeable — subpath exports mean your bundle only includes what you import
  • Zero runtime dependencies — no third-party code in production output
  • Optional Node native fast pathsigilid/native for Node-only throughput tuning
  • ESM-only — works in modern Node, edge runtimes, and all major bundlers
  • Strong TypeScript support — strict types, branded ID types, precise inference
  • Predictable behavior — explicit errors on invalid input, no silent failures
  • One package, seven entrypointsinstall sigilid, then import only what you need
  • Companion native addon package — only needed when using sigilid/native

Bundle size

All 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.


Install

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.


Quick start

import { generateId } from "sigilid";

// Default: 21 URL-safe characters using crypto.getRandomValues
generateId(); // "K7gkJ_q3vR2nL8xH5eM0w"
generateId(12); // "aX4_p9Qr2mNs"

Why sigilid?

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.


When to use the root import vs subpath exports

| 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.


API reference

sigilid — secure root

import { 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-based

import { 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 path

import { generateDefault, generateId } from "sigilid/native";

generateDefault(); // 21-character secure ID
generateDefault(32); // 32-character secure ID

// Alias that mirrors the root naming
generateId(21);
  • Node-only entrypoint.
  • Requires the companion addon package: @sigilid/native-addon.
  • Uses secure randomness and the same default alphabet as sigilid.
  • Throws a clear error if the addon is missing or the runtime is unsupported.
  • Addon install tries prebuilt binaries first, then falls back to local node-gyp build.
  • Publishing note: 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 IDs

import { 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:

  • Must start with a letter
  • Must contain only letters and digits
  • Separator is always _

Throws TypeError for invalid prefixes. Throws RangeError for invalid lengths.


sigilid/typed — branded TypeScript ID types

import { 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 helpers

import { 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 alphabets

import { 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:

  • the alphabet has fewer than 2 characters
  • the alphabet has more than 256 characters
  • the alphabet contains duplicate characters

generate(length?) uses rejection sampling to avoid modulo bias.


Built for real TypeScript apps

Branded types in practice

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">'

Validation at the edge

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
});

Tree-shaking

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.


Why not just use Nano ID?

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:

  • You want prefixed IDs and typed IDs in the same package
  • You want validation helpers that know about your ID format
  • You want stricter TypeScript ergonomics out of the box
  • You want a single library that handles the full ID lifecycle

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.


Runtime and environment notes

sigilid uses globalThis.crypto.getRandomValues, which is available in:

  • Node.js 20+ (stable, no flags required)
  • All modern browsers
  • Edge runtimes: Cloudflare Workers, Vercel Edge, Deno, Bun

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.


Benchmarking

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

Package exports

| 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.


Contributing

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.


Release and versioning

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.


License

MIT — see LICENSE.