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

oid64

v6.1.0

Published

Fast, strict, URL-safe base64 for MongoDB ObjectIds, UUIDs, integers and bigints. Zero dependencies, ESM, TypeScript.

Readme

oid64

Shorten MongoDB ObjectIds and UUIDs into URL-safe base64 strings, and decode them back. Fast, strict, zero dependencies, TypeScript.

npm version CI license bundle size

ObjectId  581653766c5dbc10f0aceb55              ->  WBZTdmxdvBDwrOtV        (24 -> 16 chars)
UUID      6d2bb408-3176-42d3-b473-3d251f19569f  ->  bSu0CDF2QtO0cz0lHxlWCf  (36 -> 22 chars)
number    6083061                               ->  XNH1
bigint    27261671373252370877777767253n        ->  WBZTdmxdvBDwrOtV

Use it for shorter URLs, share links, QR codes, slugs, cache keys and log lines. The default alphabet is A-Z a-z 0-9 - _, without padding, suitable for URL paths and query values. ObjectIds use standard base64url. UUIDs retain the oid64 5.x final-byte layout; see Format before integrating another codec.

Why oid64

  • Every input is validated. Wrong length, wrong type, characters outside the alphabet, uppercase vs lowercase, out-of-range numbers: all of it either decodes correctly or throws an Oid64Error. Nothing ever returns "undefined", NaN or garbage bytes.
  • Fast. Shared lookup tables, strict validation integrated into conversion, and optional reusable decode buffers. Compare runtimes and native codecs with the benchmarks.
  • Compatible with standard base64url. Encoded ObjectIds are byte-for-byte what Buffer#toString("base64url") produces, so other languages can decode them with their standard library.
  • Small. One runtime module, no dependencies, ESM with TypeScript declarations.
  • Portable. Tested on Node 24 and the pinned Bun version. The library uses no Node-specific APIs; other runtimes need modern JavaScript, including BigInt and private class fields.

New in 6.1.0

Default-alphabet encoders now share lookup tables, substantially reducing the memory cost of additional instances. Binary decoders leave supplied buffers unchanged on invalid input, and the alphabet is protected against runtime reassignment. Method signatures and valid encoded outputs remain compatible with 6.0.0.

This release also adds reproducible Node/Bun benchmarks and tests the installed npm archive, including TypeScript declarations. See the 6.1.0 release notes and measured results.

Install

bun add oid64@^6.1.0
# or
npm install oid64@^6.1.0

Quick start

import { oid64 } from "oid64";

// MongoDB ObjectId (24 hex chars) <-> 16 chars
oid64.fromObjectId("581653766c5dbc10f0aceb55"); // "WBZTdmxdvBDwrOtV"
oid64.toObjectId("WBZTdmxdvBDwrOtV"); // "581653766c5dbc10f0aceb55"

// UUID (36 chars, dashed) <-> 22 chars (oid64 format, not standard base64url)
oid64.fromUUID("6d2bb408-3176-42d3-b473-3d251f19569f"); // "bSu0CDF2QtO0cz0lHxlWCf"
oid64.toUUID("bSu0CDF2QtO0cz0lHxlWCf"); // "6d2bb408-3176-42d3-b473-3d251f19569f"

// Integers (0 .. Number.MAX_SAFE_INTEGER)
oid64.fromInt(6083061); // "XNH1"
oid64.toInt("XNH1"); // 6083061

// BigInts of any size (Snowflake ids, 128-bit values, ...)
oid64.fromBigInt(1234567890123456789n); // "BEiEPR96YEV"
oid64.toBigInt("BEiEPR96YEV"); // 1234567890123456789n

With the MongoDB driver

import { ObjectId } from "mongodb";
import { oid64 } from "oid64";

const doc = await users.findOne({ email });
const shortId = oid64.fromObjectId(doc._id.toHexString()); // "WBZTdmxdvBDwrOtV"

// Later, from a URL parameter:
const _id = new ObjectId(oid64.toObjectId(req.params.id));

Or skip the hex round-trip entirely and work on the raw bytes:

oid64.fromBinObjectId(doc._id.id); // ObjectId#id is a 12-byte Uint8Array
new ObjectId(oid64.toBinObjectId(shortId));

With crypto.randomUUID()

const id = oid64.fromUUID(crypto.randomUUID()); // 22 chars instead of 36

Handling bad input

import { oid64, Oid64Error } from "oid64";

try {
  oid64.toObjectId(req.params.id);
} catch (error) {
  if (error instanceof Oid64Error) {
    // "encoded ObjectId must be 16 characters, received 9"
    // "encoded ObjectId contains characters outside the alphabet"
    return res.status(400).send("invalid id");
  }
  throw error;
}

API

All methods live on an Encoder instance. Prefer oid64, the shared default instance. The alphabet is read-only at runtime, tables are private, and default-alphabet instances share their tables. Custom-alphabet instances build their own tables; reuse them across calls.

| Method | Input | Output | | --- | --- | --- | | fromObjectId(hex) | 24 hex chars, either case | 16 chars | | toObjectId(id) | 16 chars | 24 lowercase hex chars | | fromBinObjectId(bytes) | Uint8Array of 12 bytes | 16 chars | | toBinObjectId(id, out?) | 16 chars, optional target buffer | supplied buffer or new 12-byte Uint8Array | | fromUUID(uuid) | 36 chars dashed or 32 hex chars, either case | 22 chars | | toUUID(id) | 22 chars | 36 chars, lowercase, dashed | | fromBinUUID(bytes) | Uint8Array of 16 bytes | 22 chars | | toBinUUID(id, out?) | 22 chars, optional target buffer | supplied buffer or new 16-byte Uint8Array | | fromInt(n) | safe integer 0 .. 2^53-1 | 1 to 9 chars | | toInt(id) | 1 to 9 chars | safe integer | | fromBigInt(n) | non-negative bigint | 1+ chars | | toBigInt(id) | 1+ chars | bigint |

Every method throws Oid64Error (a subclass of Error) on invalid input. Node Buffer is accepted wherever a Uint8Array is expected.

toBinObjectId and toBinUUID accept an optional output buffer of at least 12 or 16 bytes and return that same buffer, including any extra capacity. Only the first 12 or 16 bytes are written. Invalid input leaves the entire buffer unchanged.

const scratch = new Uint8Array(12);
for (const shortId of shortIds) {
  oid64.toBinObjectId(shortId, scratch);
  consumeSynchronously(scratch); // The next decode overwrites these bytes.
}

If a consumer retains the bytes or uses them asynchronously, give it a copy or a separate buffer. When MongoDB bytes are already available, fromBinObjectId(doc._id.id) also avoids converting them to hex first.

Custom alphabet

import { Encoder } from "oid64";

const encoder = new Encoder("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_");
encoder.fromObjectId("581653766c5dbc10f0aceb55"); // "wbztDMXDVbdWRoTv"

The alphabet must be exactly 64 unique ASCII characters. The constructor throws otherwise. A custom alphabet changes the output but not its length. URL safety depends on the characters you choose; ASCII validation alone does not guarantee it.

Exports

import { Encoder, Oid64Error, DEFAULT_BASE, oid64 } from "oid64";

Format

All formats use the configured 64-character alphabet without padding. Their bit layouts differ:

  • ObjectId: 96 bits divide evenly into 16 groups of 6 bits. With the default alphabet, the result is identical to Buffer.from(bytes).toString("base64url").
  • UUID: the first 15 bytes use standard base64 grouping (20 characters). The final byte b becomes alphabet[b >> 6] + alphabet[b & 63], preserving the 5.x format. Standard base64url instead uses alphabet[b >> 2] + alphabet[(b & 3) << 4]. For the UUID above, oid64 ends in Cf, standard base64url in nw. Use an oid64-compatible decoder; the two formats cannot be reliably autodetected. Decoding rejects a final pair greater than 255, so every UUID has one accepted encoding. UUID parsing checks hex and separator placement, not UUID version or variant bits.
  • Integers: numeric base 64, most-significant group first, no leading zero characters; 0 encodes as A with the default alphabet. This is not byte-oriented base64url. Decoding accepts leading zero characters within the input length limit, so numeric encodings are not required to be canonical. toBigInt has no library length limit; applications can enforce one before decoding externally supplied values.

Performance

Run the same deterministic corpus of 1,024 varying IDs on each runtime:

bun run bench:node   # Node, with explicit garbage collection available
bun run bench:bun    # Bun
bun run bench:memory # Node import cost and retained memory per encoder

bun run bench defaults to Node. The benchmark prints CPU, runtime, latency distributions and estimates of allocation/GC overhead. It compares equivalent operations against a pinned 5.2.1 snapshot, and ObjectId hex encoding against native Buffer. All inputs are valid; native Buffer and 5.x do not provide the current strict validation contract. Integer comparisons with 5.x stay below 2^31.

Corpus generation happens outside the timed region. The measurements include selecting the next input and dispatching the conversion, and represent warmed-up throughput. Run on an idle machine; results depend on CPU, runtime and GC. Constructor and memory measurements cover costs that throughput alone misses. No benchmark runs as a timing gate in CI.

A dated sample result records the environment and measured values.

For application performance, reuse oid64, pass existing bytes directly, and reuse output buffers when their lifetime permits it.

Can the output be even shorter?

An arbitrary 96-bit ObjectId needs at least 16 characters with this 64-character alphabet. RFC 3986 defines 66 unreserved URL characters; even using all 66 still requires 16 characters for 96 bits. Shorter output therefore needs a different alphabet with different escaping rules, a smaller ID space, or a stored mapping. For example, a 64-bit ID takes at most 11 characters with fromBigInt; an application can also store a shorter slug alongside its ObjectId.

Migrating from 5.x

Valid ObjectId and UUID encodings produced by 5.x keep their format. Nonnegative integers below 2^31 keep their encoding except zero. Existing strings resulting from the old integer overflow cannot recover the original value; regenerate them from the source IDs.

  • The package is ESM-only and requires Node 24+. require() still works, Node 24 loads ES modules natively.
  • Invalid input throws Oid64Error instead of returning nonsense.
  • fromInt and toInt are correct for the full safe-integer range; 5.x silently overflowed at 2^31.
  • fromInt(0) returns "A" instead of ""; toInt("") now throws. Migrate stored empty zero encodings explicitly.
  • The noLookup constructor flag and the hexToBase / baseToHex properties are gone.
  • fromBigInt / toBigInt are back.

See CHANGELOG.md for the full list.

Development

bun install
bun run check      # lint + typecheck + tests + build + installed-package checks
bun test           # tests only
bun run bench:node # Node benchmarks

CI uses the Bun version in packageManager and Node 24. Package checks install a temporary npm archive, exercise ESM and require(), and compile a TypeScript consumer without Bun ambient types. Run bun run build && bun run test:package to repeat that check alone.

Releases are published to npm with provenance when a version-matching v*.*.* tag is pushed. The release workflow runs the full check once before publishing. Local npm publish runs the same check through prepublishOnly.

Credits

Forked from objectid64 by Maga D. Zandaqo. Rewritten for strict validation, lookup-table performance and modern tooling.

License

MIT