ULID
A fast, sortable, URL-safe unique identifier generator that beats UUIDs for time-ordered keys.
Repository Health
Technical Analysis
ULID (Universally Unique Lexicographically Sortable Identifier) is a zero-dependency JavaScript/TypeScript library that generates 128-bit unique identifiers encoded as 26-character, case-insensitive, URL-safe strings using Crockford’s Base32. Unlike random UUIDs, ULIDs sort lexicographically by creation time, which keeps database indexes ordered and avoids the write-amplification and page-fragmentation problems that fully random primary keys cause in B-tree indexes.
The library exposes a small, ergonomic API: a single ulid() call for one-off IDs, a monotonicFactory() for strictly increasing IDs within the same millisecond, plus helpers to validate IDs, encode/decode the embedded timestamp, and convert between ULID and UUID representations. It ships as dual ESM/CommonJS builds with full TypeScript types, runs in Node.js, browsers, React Native, web workers, and edge runtimes, and includes a small CLI for generating IDs from the shell.
What You Get
ulid()— generate a single sortable, URL-safe ID with automatic PRNG detection (Web Crypto in browsers, Node’s crypto module server-side)monotonicFactory()— a factory that guarantees strictly increasing IDs even when multiple calls happen within the same millisecondisValid(),decodeTime(),encodeTime()— validate ULIDs and extract or encode their embedded timestampulidToUUID()/uuidToULID()— lossless conversion between ULID and standard UUID string formats- A bundled CLI (
npx ulid --count 15) for generating IDs from the command line, plus dual ESM/CJS builds with full TypeScript declarations for Node, browser, React Native, and edge runtimes
Common Use Cases
- Primary keys for relational or document databases where insert order should match index order, avoiding the fragmentation caused by random UUID v4 keys
- Distributed ID generation across services or edge functions without a central sequence generator or coordination service
- Event and log record IDs where chronological sortability by ID alone (without a separate timestamp column) simplifies queries
- Migrating existing UUID-keyed systems to a sortable scheme via the built-in ULID <-> UUID conversion helpers
Under The Hood
Architecture ULID’s implementation is a small set of tightly-scoped modules: constants.ts defines the Crockford Base32 alphabet and fixed byte lengths, crockford.ts implements bit-level Base32 encode/decode plus a base32 string-increment routine used for monotonic generation, ulid.ts composes these into the public ulid()/monotonicFactory()/decodeTime()/encodeTime()/isValid() functions and handles PRNG auto-detection across Node, browser, web-worker, and React Native globals, uuid.ts layers UUID string conversion on top of the same Crockford codec, and error.ts centralizes a typed ULIDError with a stable ULIDErrorCode enum used consistently across all failure paths. index.ts re-exports the public surface, and cli.ts is a thin argument-parsing wrapper around monotonicFactory() for the bundled CLI. The layering is clean and unidirectional: constants -> crockford codec -> ulid/uuid logic -> public API, with no circular dependencies.
Tech Stack The library is written in TypeScript with zero runtime dependencies, targeting Node 18+ and modern browsers. It builds dual ESM and CommonJS artifacts (plus a browser-specific bundle) via Rollup, generates .d.ts/.d.cts type declarations with tsc, and validates the resulting package exports with @arethetypeswrong/cli to catch ESM/CJS interop mistakes before publish — a level of build rigor uncommon in small utility libraries.
Code Quality The test suite (Vitest, under test/node/) exercises the core surface directly: ulid.spec.ts covers decodeTime, encodeTime, and monotonic-factory behavior across same-millisecond and cross-millisecond calls with a stubbed PRNG for determinism; crockford.spec.ts and uuid.spec.ts cover the Base32 codec and UUID conversion round-trips. Error handling is consistent and typed via ULIDError/ULIDErrorCode rather than generic thrown strings, and Prettier plus a test:format script enforce consistent style. Test coverage is solid for the core encode/decode/monotonic paths though thinner around CLI argument parsing and PRNG-detection edge cases.
API Design The public API is minimal and low-boilerplate: import { ulid } from "ulid"; ulid() is the entire integration for the common case, with JSDoc @example blocks on every exported function showing real input/output pairs. Naming is consistent (encodeTime/decodeTime, ulidToUUID/uuidToULID) and the monotonic-factory pattern keeps the stateful variant explicit and opt-in rather than defaulting to hidden global state.