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

nope-id

v1.2.0

Published

A tiny, fast, secure, URL-friendly unique string ID generator. ESM & CommonJS. Nanoid alternative with extra features (prefixed IDs, sortable IDs, UUID, distributed IDs).

Readme

nope-id

A tiny, secure, URL-friendly unique string ID generator for JavaScript.

A faster, more secure alternative to nanoid with extra features!

  • Faster - 2x to 5x faster than nanoid (CSPRNG, full URL-safe alphabet); wins all 5 core benchmarks (see benchmarks)
  • Security Hardened - Timing attack prevention, modulo bias elimination, prototype pollution protection (see security)
  • Well Tested - 307 tests including security & entropy tests (see testing)
  • Cryptographically Secure - Uses webcrypto.getRandomValues() (CSPRNG)
  • Zero Dependencies - No external dependencies
  • URL-safe - Uses A-Za-z0-9_- characters
  • Dual Module - Works with both ESM (import) and CommonJS (require)
  • TypeScript - Full type definitions included
  • Collision-resistant - Monotonic sortable IDs, distributed-safe IDs
  • Many ID Formats - UUID v4 & v7, ULID (spec-compliant + monotonic factory), Snowflake, MongoDB ObjectId
  • Extra Features - Prefixed IDs, sortable IDs, Sqids (reversible encoding), typed IDs, format validators, and more!

Installation

npm install nope-id

Quick Start

ES Modules (import)

import { nopeid } from 'nope-id'

const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

CommonJS (require)

const { nopeid } = require('nope-id')

const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

Default Export

// ES Modules
import nopeid from 'nope-id'
const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

// CommonJS
const nopeid = require('nope-id')
const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

API Reference

Core Functions

nopeid(size = 21)

Generates a secure, URL-safe unique ID.

// ES Modules
import { nopeid } from 'nope-id'

nopeid()    // "V1StGXR8_Z5jdHi6B-myT" (21 characters)
nopeid(10)  // "IRFa-VaY2b" (10 characters)
nopeid(32)  // "V1StGXR8_Z5jdHi6B-myTV1StGXR8_Z5" (32 characters)
nopeid(0)   // "" (empty string for zero/negative)

// CommonJS
const { nopeid } = require('nope-id')
const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

customAlphabet(alphabet, defaultSize = 21)

Creates a custom ID generator with your own alphabet.

// ES Modules
import { customAlphabet } from 'nope-id'

// Hex IDs
const hexId = customAlphabet('0123456789abcdef', 16)
hexId()   // "4f90d13a42f17f80"
hexId(8)  // "a3b2c1d4"

// Numbers only
const numericId = customAlphabet('0123456789', 8)
numericId() // "48293751"

// Custom characters
const customId = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 6)
customId() // "BKWXYZ"

// Binary IDs
const binaryId = customAlphabet('01', 16)
binaryId() // "1010110011001010"

// CommonJS
const { customAlphabet } = require('nope-id')
const hexGen = customAlphabet('0123456789abcdef', 16)
const id = hexGen() // "4f90d13a42f17f80"

customRandom(alphabet, defaultSize, getRandom)

Creates a custom ID generator with your own random function.

// ES Modules
import { customRandom } from 'nope-id'

// Use custom random source
const customGen = customRandom(
  'abcdef',
  10,
  (size) => new Uint8Array(size).fill(1)  // Deterministic for testing
)
customGen() // Deterministic output

// CommonJS
const { customRandom } = require('nope-id')

random(bytes)

Generates cryptographically secure random bytes.

// ES Modules
import { random } from 'nope-id'

const bytes = random(16)  // Uint8Array(16) with random bytes
const bytes2 = random(32) // Uint8Array(32) with random bytes

// CommonJS
const { random } = require('nope-id')
const bytes = random(16)

ID Generation Functions

prefixedId(prefix, size = 21, separator = '_')

Generates an ID with a prefix - perfect for database entries!

// ES Modules
import { prefixedId } from 'nope-id'

prefixedId('user')           // "user_V1StGXR8_Z5jdHi6B-myT"
prefixedId('order', 10)      // "order_IRFa-VaY2b"
prefixedId('prod', 8, '-')   // "prod-Z5jdHi6B"
prefixedId('cust', 12, ':')  // "cust:a1b2c3d4e5f6"

// Real-world examples
const userId = prefixedId('usr')       // "usr_V1StGXR8_Z5jdHi6B-myT"
const orderId = prefixedId('ord')      // "ord_IRFa-VaY2bKwxyz..."
const productId = prefixedId('prod')   // "prod_Z5jdHi6B-myT..."
const transactionId = prefixedId('txn') // "txn_8_Z5jdHi6B..."

// CommonJS
const { prefixedId } = require('nope-id')
const id = prefixedId('user') // "user_V1StGXR8_Z5jdHi6B-myT"

sortableId(size = 22)

Generates a ULID-like sortable ID with monotonic guarantee. Uses Crockford's Base32 for lexicographic sorting.

Features:

  • 10 chars timestamp + 12 chars random = 22 chars default
  • Chronologically sortable (lexicographic order)
  • Same-millisecond IDs are guaranteed monotonically increasing
  • Can decode timestamp with decodeTime()
  • Sizes > 22 are padded with extra Crockford Base32 random chars; sizes < 22 truncate to the timestamp prefix (use size ≥ 22 to keep the monotonic guarantee)
// ES Modules
import { sortableId, decodeTime } from 'nope-id'

const id1 = sortableId() // "01HGW2BBK0QZRMTX12345A"
// wait some time...
const id2 = sortableId() // "01HGW2BBK1ABCDEFGHIJKL"

// Chronologically sortable
console.log(id1 < id2) // true

// Same millisecond - monotonically increasing
const ids = []
for (let i = 0; i < 5; i++) {
  ids.push(sortableId())
}
// All IDs are unique and strictly increasing
// ids[0] < ids[1] < ids[2] < ids[3] < ids[4]

// Custom size
sortableId(30) // 30 character sortable ID

// Decode timestamp
const id = sortableId()
const date = decodeTime(id)
console.log(date) // Date object when ID was created

// CommonJS
const { sortableId, decodeTime } = require('nope-id')
const id = sortableId()
const timestamp = decodeTime(id)

decodeTime(sortableIdStr)

Extracts the timestamp from a sortable ID.

// ES Modules
import { sortableId, decodeTime } from 'nope-id'

const id = sortableId()
const date = decodeTime(id)

console.log(date)            // 2024-01-15T10:30:00.000Z
console.log(date.getTime())  // 1705314600000

// Error handling
try {
  decodeTime('invalid')  // throws Error
} catch (e) {
  console.log('Invalid sortable ID')
}

// CommonJS
const { sortableId, decodeTime } = require('nope-id')

generateMany(count, size = 21)

Generates multiple unique IDs at once.

// ES Modules
import { generateMany } from 'nope-id'

const ids = generateMany(5)
// ["V1StGXR8_Z5jdHi6B-myT", "IRFa-VaY2bKwxyz...", ...]

const shortIds = generateMany(100, 8)
// 100 short IDs of 8 characters each

generateMany(0)   // [] (empty array)
generateMany(-5)  // [] (empty array)

// Bulk insert example
const userIds = generateMany(1000, 21)
await db.users.insertMany(
  userIds.map(id => ({ id, createdAt: new Date() }))
)

// CommonJS
const { generateMany } = require('nope-id')
const ids = generateMany(100)

uuid()

Generates a UUID v4 compatible string.

// ES Modules
import { uuid } from 'nope-id'

uuid() // "110ec58a-a0f2-4ac4-8393-c866d813b8d1"
uuid() // "f47ac10b-58cc-4372-a567-0e02b2c3d479"

// Use in databases expecting UUID
const user = {
  id: uuid(),
  name: 'John'
}

// CommonJS
const { uuid } = require('nope-id')
const id = uuid()

slugId(size = 12)

Generates a slug-friendly ID (lowercase + numbers only). Perfect for URLs!

// ES Modules
import { slugId } from 'nope-id'

slugId()   // "a8b3c9d2e1f4"
slugId(8)  // "x7y2z9w4"
slugId(16) // "a1b2c3d4e5f6g7h8"

// URL-friendly usage
const articleSlug = `my-article-${slugId()}`
// "my-article-a8b3c9d2e1f4"

// CommonJS
const { slugId } = require('nope-id')
const slug = slugId()

shortId(size = 8)

Generates a short ID without similar-looking characters (no 0/O, 1/l/I). Perfect for user-facing codes!

// ES Modules
import { shortId } from 'nope-id'

shortId()   // "aBc7XyZ9"
shortId(6)  // "Kp3Wn8"
shortId(10) // "aBc7XyZ9Kp"

// Perfect for:
// - Verification codes
// - Short URLs
// - User-readable references
// - Ticket numbers

const verificationCode = shortId(6) // "Kp3Wn8"
const ticketNumber = shortId(8)     // "aBc7XyZ9"

// CommonJS
const { shortId } = require('nope-id')
const code = shortId(6)

nopeidAsync(size = 21)

Async version for non-blocking operations.

// ES Modules
import { nopeidAsync } from 'nope-id'

const id = await nopeidAsync()     // "V1StGXR8_Z5jdHi6B-myT"
const id2 = await nopeidAsync(32)  // 32 character async ID

// Parallel generation
const ids = await Promise.all([
  nopeidAsync(),
  nopeidAsync(),
  nopeidAsync()
])

// CommonJS
const { nopeidAsync } = require('nope-id')
const id = await nopeidAsync()

Distributed System Functions

getFingerprint()

Gets a unique fingerprint for the current process/device. Generated once and cached.

// ES Modules
import { getFingerprint } from 'nope-id'

const fp = getFingerprint() // "aB3x" (4 characters)
const fp2 = getFingerprint() // "aB3x" (same value, cached)

// Useful for distributed systems to identify origin
console.log(`ID generated by process: ${getFingerprint()}`)

// CommonJS
const { getFingerprint } = require('nope-id')
const fingerprint = getFingerprint()

distributedId(size = 25)

Generates a distributed-safe ID with process fingerprint. Perfect for multi-node environments!

Format: fingerprint_randomPart

// ES Modules
import { distributedId, getFingerprint } from 'nope-id'

distributedId()   // "aB3x_V1StGXR8_Z5jdHi6B" (25 chars)
distributedId(30) // "aB3x_V1StGXR8_Z5jdHi6B-myT1" (30 chars)

// In a distributed system, each node generates IDs with its own fingerprint
// Node 1: "aB3x_V1StGXR8_Z5jdHi6B"
// Node 2: "kL9m_IRFa-VaY2bKwxyz12"
// Node 3: "pQ7r_Z5jdHi6B-myTV1St8"

// Identify which node generated an ID
const id = distributedId()
const nodeFingerprint = id.split('_')[0]
console.log(`Generated by node: ${nodeFingerprint}`)

// CommonJS
const { distributedId } = require('nope-id')
const id = distributedId()

Utility Functions

isValid(id, alphabet = urlAlphabet)

Validates if a string is a valid ID for the given alphabet.

// ES Modules
import { isValid, urlAlphabet, alphabets } from 'nope-id'

isValid('V1StGXR8_Z5jdHi6B-myT')  // true
isValid('')                        // false
isValid(null)                      // false
isValid('abc@#$')                  // false (invalid chars)

// Validate with custom alphabet
isValid('abc123', 'abc123')        // true
isValid('ABC', 'abc')              // false
isValid('12345678', alphabets.numbers) // true

// CommonJS
const { isValid } = require('nope-id')
const valid = isValid('V1StGXR8_Z5jdHi6B-myT')

collisionProbability(idLength, alphabetSize = 64)

Calculate collision probability for given parameters.

// ES Modules
import { collisionProbability } from 'nope-id'

const info = collisionProbability(21)
console.log(info)
// {
//   totalPossible: 9007199254740991,   // clamped to MAX_SAFE_INTEGER (use totalPossibleBigInt for exact)
//   totalPossibleBigInt: 85070591730234615865843651857942052864n, // 64^21 ≈ 8.5e37
//   probabilityForBillion: 0,          // ~5.9e-21, rounds to 0 in double precision
//   safeCount: 1.086e+19,              // ~50% collision after generating this many IDs
//   yearsFor1Percent: 4.133e+7         // years at 1 ID/ms before 1% collision
// }

// Compare different lengths
collisionProbability(8)   // Much higher collision risk
collisionProbability(32)  // Very low collision risk

// Custom alphabet size
collisionProbability(21, 62)  // Alphanumeric only
collisionProbability(21, 16)  // Hex only

// CommonJS
const { collisionProbability } = require('nope-id')
const info = collisionProbability(21)

Pre-built Alphabets

// ES Modules
import { alphabets, customAlphabet } from 'nope-id'

// Available alphabets
alphabets.alphanumeric    // "0-9A-Za-z" (62 chars)
alphabets.lowercase       // "a-z" (26 chars)
alphabets.uppercase       // "A-Z" (26 chars)
alphabets.numbers         // "0-9" (10 chars)
alphabets.hexLower        // "0-9a-f" (16 chars)
alphabets.hexUpper        // "0-9A-F" (16 chars)
alphabets.nolookalikes    // No confusing chars (49 chars)
alphabets.nolookalikesSafe // Extra safe version (34 chars)
alphabets.binary          // "01" (2 chars)
alphabets.octal           // "0-7" (8 chars)
alphabets.base32          // RFC 4648 Base32 (32 chars)
alphabets.base32Lower     // Lowercase Base32 (32 chars)
alphabets.base58          // Bitcoin Base58 (58 chars)
alphabets.filename        // Filename-safe (64 chars)

// Usage with customAlphabet
const hexGen = customAlphabet(alphabets.hexLower, 32)
hexGen() // "a1b2c3d4e5f67890abcdef1234567890"

const base58Gen = customAlphabet(alphabets.base58, 22)
base58Gen() // Bitcoin-style ID

const safeGen = customAlphabet(alphabets.nolookalikesSafe, 8)
safeGen() // Very readable code

// CommonJS
const { alphabets, customAlphabet } = require('nope-id')
const gen = customAlphabet(alphabets.hexLower, 16)

Additional ID Formats & Helpers

These secure-only generators and helpers are exposed as tree-shakeable named exports and never touch the hot path of nopeid(); import only what you use.

uuidv7()

Time-ordered UUID (RFC 9562), database-index friendly and sortable by creation time. The first 48 bits encode the Unix-ms timestamp.

import { uuidv7, isValidUUID } from 'nope-id'

uuidv7()                  // "0192f3c1-8e2a-7b3c-9d4e-5f60718293a4"
isValidUUID(uuidv7(), 7)  // true

ulid(seedTime?) & monotonicFactory()

Spec-compliant 26-char ULID (Crockford Base32: 10 timestamp + 16 random). ulid() uses fresh randomness per call; monotonicFactory() returns a generator with isolated state that guarantees strictly increasing IDs within the same millisecond (without touching the global sortableId() state).

import { ulid, monotonicFactory, decodeTime } from 'nope-id'

ulid()              // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
decodeTime(ulid())  // Date

const next = monotonicFactory()
next() < next()     // true (same ms, strictly increasing)

snowflakeFactory(options), snowflake() & decodeSnowflake(id)

Twitter-style 64-bit distributed IDs returned as strings (BigInt-safe). Layout: 41-bit timestamp · 10-bit node id · 12-bit sequence. Each factory owns its own sequence state (coordination-free per node).

import { snowflakeFactory, decodeSnowflake, snowflake } from 'nope-id'

const next = snowflakeFactory({ nodeId: 1 })  // optional: { epoch }
const id = next()                              // "1838219834728448001"
decodeSnowflake(id)  // { timestamp: Date, nodeId: 1, sequence: 0 }

snowflake()  // default single-node generator (node id derived from fingerprint)

objectId() & decodeObjectIdTime(id)

MongoDB ObjectId-compatible 24-char hex (4-byte timestamp + 5-byte per-process value + 3-byte counter).

import { objectId, decodeObjectIdTime } from 'nope-id'

objectId()                      // "65f1c3e2a1b2c3d4e5f60718"
decodeObjectIdTime(objectId())  // Date

sqidsFactory(options): reversible integer encoding

Encode arrays of non-negative integers into short, URL-safe, reversible strings, for example to hide sequential database IDs in URLs. This is obfuscation, not encryption.

import { sqidsFactory } from 'nope-id'

const sqids = sqidsFactory()        // { alphabet?, minLength?, blocklist? }
const id = sqids.encode([1, 2, 3])  // "86Rf07"
sqids.decode(id)                    // [1, 2, 3]

The default has no profanity blocklist (to stay tiny). Pass your own blocklist if you need one.

defineId(prefix, options?): typed prefixed IDs

Stripe-style typed IDs with a generator, a type guard, and a parser. In TypeScript the generated id is typed as `${prefix}_${string}`.

import { defineId } from 'nope-id'

const UserId = defineId('user')   // { size?, separator?, alphabet? }
const id = UserId.generate()      // type: `user_${string}`
UserId.is(id)                     // true (type guard narrows the type)
UserId.parse('user_abc')          // { prefix: 'user', id: 'abc' }  (or null)

isValidUUID(id, version?) & isValidULID(id)

Format validators for UUID and ULID strings.

import { isValidUUID, isValidULID } from 'nope-id'

isValidUUID('110ec58a-a0f2-4ac4-8393-c866d813b8d1')  // true
isValidUUID(uuidv7(), 7)                              // true (version-pinned)
isValidULID('01ARZ3NDEKTSV4RRFFQ69G5FAV')             // true

These formats use cryptographic randomness and are secure-version only; they are not part of nope-id/non-secure.


Non-Secure Version

For non-critical use cases (UI element IDs, temporary keys, etc.), you can use the faster non-secure version:

// ES Modules
import { nopeid, sortableId, prefixedId } from 'nope-id/non-secure'

nopeid()      // Uses Math.random() - faster but not cryptographically secure
sortableId()  // Still has monotonic guarantee
prefixedId('user') // Still works the same way

// CommonJS
const { nopeid, sortableId } = require('nope-id/non-secure')
const id = nopeid()

Warning: Do not use for security-sensitive purposes like tokens, passwords, or session IDs!

Use cases for non-secure version:

  • UI element IDs (React keys, etc.)
  • Temporary file names
  • Log correlation IDs
  • Test fixtures
  • Any case where cryptographic security is not required

Real-World Examples

Database Primary Keys

// ES Modules
import { prefixedId, sortableId } from 'nope-id'

// User table with prefixed IDs
const user = {
  id: prefixedId('usr'),  // "usr_V1StGXR8_Z5jdHi6B-myT"
  email: '[email protected]'
}

// Orders with sortable IDs (auto-sorted by creation time)
const order = {
  id: sortableId(),  // "01HGW2BBK0QZRMTX12345A"
  userId: user.id,
  total: 99.99
}

// CommonJS
const { prefixedId, sortableId } = require('nope-id')

API Token Generation

// ES Modules
import { nopeid, prefixedId } from 'nope-id'

// API keys
const apiKey = prefixedId('sk', 32)  // "sk_V1StGXR8_Z5jdHi6B-myTV1StGXR8_"

// Refresh tokens
const refreshToken = nopeid(64)  // 64 char secure token

// CommonJS
const { nopeid, prefixedId } = require('nope-id')

URL Shortener

// ES Modules
import { slugId, shortId } from 'nope-id'

// Short URLs
const shortUrl = `https://short.ly/${slugId(8)}`
// "https://short.ly/a8b3c9d2"

// User-friendly codes
const shareCode = shortId(6)  // "Kp3Wn8" (no confusing characters)

// CommonJS
const { slugId, shortId } = require('nope-id')

Distributed Systems

// ES Modules
import { distributedId, sortableId, getFingerprint } from 'nope-id'

// Multi-node safe IDs
const eventId = distributedId()
// "aB3x_V1StGXR8_Z5jdHi6B"

// Log with node identification
console.log(`[${getFingerprint()}] Processing event ${eventId}`)

// Time-series data with sortable IDs
const metric = {
  id: sortableId(),
  timestamp: new Date(),
  value: 42
}

// CommonJS
const { distributedId, getFingerprint } = require('nope-id')

React / Next.js

// ES Modules (React)
import { nopeid, prefixedId } from 'nope-id'

function TodoList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={nopeid()}>{item.text}</li>
      ))}
    </ul>
  )
}

function CreateUser() {
  const [userId] = useState(() => prefixedId('usr'))
  // ...
}

// For non-critical UI keys, use non-secure version for better performance
import { nopeid } from 'nope-id/non-secure'

Express.js API

// CommonJS
const express = require('express')
const { prefixedId, sortableId, uuid } = require('nope-id')

const app = express()

app.post('/users', (req, res) => {
  const user = {
    id: prefixedId('usr'),
    ...req.body
  }
  // Save user...
  res.json(user)
})

app.post('/orders', (req, res) => {
  const order = {
    id: sortableId(),  // Sortable by creation time
    ...req.body
  }
  // Save order...
  res.json(order)
})

// For systems expecting UUID
app.post('/legacy-api', (req, res) => {
  const record = {
    id: uuid(),
    ...req.body
  }
  res.json(record)
})

Comparison with nanoid

| Feature | nope-id | nanoid | |---------|---------|--------| | Secure random | ✅ | ✅ | | URL-safe | ✅ | ✅ | | TypeScript | ✅ | ✅ | | ESM + CommonJS | ✅ | ⚠️ ESM-only (dropped CJS in v4) | | Prefixed IDs | ✅ | ❌ | | Sortable IDs (ULID-like) | ✅ | ❌ | | Monotonic guarantee | ✅ | ❌ | | Timestamp decoding | ✅ | ❌ | | Distributed IDs | ✅ | ❌ | | Process fingerprint | ✅ | ❌ | | UUID v4 | ✅ | ❌ | | UUID v7 (time-ordered) | ✅ | ❌ | | ULID (spec-compliant) | ✅ | ❌ | | Monotonic ULID factory | ✅ | ❌ | | Snowflake IDs | ✅ | ❌ | | MongoDB ObjectId | ✅ | ❌ | | Sqids (reversible encoding) | ✅ | ❌ | | Typed IDs (TS template types) | ✅ | ❌ | | Format validators (UUID/ULID) | ✅ | ❌ | | Slug IDs | ✅ | ❌ | | Short IDs (no lookalikes) | ✅ | ❌ | | Batch generation | ✅ | ❌ | | Validation | ✅ | ❌ | | Collision calculator | ✅ | ❌ | | Pre-built alphabets | ✅ (14) | Limited |


Browser Support

Works in all modern browsers with Web Crypto API support.

<script type="module">
  import { nopeid, prefixedId } from 'https://unpkg.com/nope-id/index.browser.js'

  console.log(nopeid())        // "V1StGXR8_Z5jdHi6B-myT"
  console.log(prefixedId('user')) // "user_V1StGXR8_..."
</script>

Security

nope-id is designed with security as a top priority. We've implemented multiple security hardening measures that go beyond basic cryptographic randomness.

Cryptographic Security

  • Node.js: webcrypto.getRandomValues() (CSPRNG)
  • Browser: crypto.getRandomValues() (CSPRNG)

Security Hardening

| Security Feature | Description | |-----------------|-------------| | Timing Attack Prevention | isValid() uses constant-time comparison to prevent timing side-channel attacks that could leak information about valid characters | | Modulo Bias Elimination | Uses rejection sampling to ensure perfectly uniform distribution for all alphabet sizes (not just powers of 2) | | Prototype Pollution Protection | alphabets object is frozen with null prototype - immune to prototype pollution attacks | | Integer Overflow Protection | collisionProbability() uses BigInt for accurate calculations with astronomically large numbers | | DoS Prevention | sortableId() has iteration limits to prevent infinite loops from frozen system clocks | | Buffer Safety | Pool management handles >65536 byte requests safely with chunked filling |

Best Practices

  • Use the secure version (default) for tokens, session IDs, API keys
  • Use sortableId() for time-based ordering with collision resistance
  • Use distributedId() in multi-node deployments
  • Only use nope-id/non-secure for non-security-critical purposes

Testing

nope-id has comprehensive test coverage with 307 tests across 6 test suites, including security-specific tests.

Run Tests

# Run all tests
npm test

# Run specific test suites
npm run test:core        # Core functions (nopeid, customAlphabet, random)
npm run test:features    # Features (prefixedId, sortableId, uuid, etc.)
npm run test:utils       # Utilities (isValid, collisionProbability)
npm run test:non-secure  # Non-secure version tests
npm run test:idtypes     # New ID types (uuidv7, ulid, snowflake, objectId)
npm run test:encoding    # Sqids, typed IDs, format validators

Test Coverage

| Test Suite | Tests | Description | |------------|-------|-------------| | Core | 81 | nopeid, customAlphabet, customRandom, random, alphabets | | Features | 78 | prefixedId, sortableId, uuid, slugId, shortId, distributedId | | Utils | 56 | isValid, collisionProbability, security tests | | Non-Secure | 29 | Math.random() based version | | ID Types | 31 | uuidv7, ulid, monotonicFactory, snowflake, objectId | | Encoding | 32 | sqids, defineId, isValidUUID, isValidULID | | Total | 307 | All tests passing |

Security Tests

We have dedicated security tests that verify our hardening measures:

📦 isValid() Security
  ✅ rejects strings with null bytes
  ✅ rejects strings with unicode zero-width chars
  ✅ constant-time validation (timing attack prevention)

📦 Prototype Pollution Prevention
  ✅ alphabets object is frozen
  ✅ alphabets has null prototype
  ✅ alphabets cannot be modified
  ✅ alphabets does not inherit Object.prototype properties

📦 Modulo Bias Prevention (customAlphabet)
  ✅ uniform distribution for non-power-of-2 alphabet
  ✅ uniform distribution for 3-char alphabet

📦 Integer Overflow Prevention
  ✅ collisionProbability returns BigInt for large values
  ✅ BigInt is accurate for values exceeding MAX_SAFE_INTEGER
  ✅ totalPossible is clamped to MAX_SAFE_INTEGER

📦 Security: Entropy Quality
  ✅ random bytes have good entropy distribution
  ✅ generated IDs have good character distribution
  ✅ no predictable patterns in sequential IDs
  ✅ chi-square test for randomness

Stress Tests

📦 Stress Tests
  ✅ rapid sequential generation (10000 IDs) - all unique
  ✅ pool exhaustion and refill cycle
  ✅ varying sizes in sequence
  ✅ alternating between different generators

Randomness Comparison Test (vs nanoid)

We have a dedicated randomness comparison test against nanoid:

npm run test:randomness

| Test | nope-id | nanoid | |------|---------|--------| | Chi-Square Distribution | ✅ χ²=44.69 | ✅ χ²=48.96 | | Uniqueness (100K IDs) | ✅ 0 duplicates | ✅ 0 duplicates | | Bit Distribution | ✅ 50.03/49.97% | ✅ 49.95/50.05% | | Sequential Correlation | ✅ 0.316 avg | ✅ 0.329 avg | | Alphabet Coverage | ✅ 64/64 (100%) | ✅ 64/64 (100%) | | Modulo Bias (3-char) | ✅ 1.05% deviation | ✅ 1.15% deviation |


Performance

About these numbers. These tables are refreshed automatically by a GitHub Actions workflow on every PR against main, running on a shared ubuntu-latest runner. CI runners are typically slower than modern developer machines and production servers, so on real hardware nope-id often hits higher numbers than what you see here. Run npm run benchmark locally to see numbers on your own machine.

Last refreshed: 2026-05-23, Node v26.x, ubuntu-latest (GitHub Actions).

nope-id vs nanoid Benchmark

nope-id wins all 5 core benchmarks vs nanoid 5.1.11, and ships many extra ID formats and security hardening on top.

Run the benchmark yourself:

npm run benchmark

Results (Node.js 20+, nanoid 5.1.11, auto-calibrated ~120 ms × best of 7 trials, with a global warmup for fairness; absolute numbers vary by machine):

| Test | nanoid 5.1.11 | nope-id | Winner | |------|--------|---------|--------| | Basic (21 chars) | ~5.1M ops/sec | ~12.3M ops/sec | nope-id ~2.4x | | Small (10 chars) | ~9.6M ops/sec | ~13.7M ops/sec | nope-id ~1.4x | | Large (64 chars) | ~2.0M ops/sec | ~9.6M ops/sec | nope-id ~4.9x | | Custom Alphabet | ~5.6M ops/sec | ~6.9M ops/sec | nope-id ~1.3x | | Batch (100 IDs) | ~55K ops/sec | ~124K ops/sec | nope-id ~2.3x |

Result: nope-id wins 5/5 against nanoid for URL-safe IDs, while providing many extra features and security hardening.

Where the speed comes from (and where it doesn't)

nope-id is the fastest JavaScript library for a specific, very common job: a cryptographically secure, URL-safe id over a full 64-character alphabet (the nanoid niche). In that category it beats nanoid (around 2.8x at the default 21-char size, up to 4.9x for 64-char IDs), and it also produces UUIDv7 and ULID far faster than the dedicated packages (roughly 6x the uuid package's v7 and ~50x the ulid package).

The speed comes from the engineering, not from cutting corners on randomness:

  • Pre-translated pool: the CSPRNG byte pool is translated in place to alphabet character codes on refill, so generating an ID is one Buffer.toString('latin1') call instead of 21 string concatenations. This replaces the per-character ConsString chain with a single V8 one-byte string allocation.
  • 16-bit batch refill: the in-place translation walks the pool in 16-bit chunks via a precomputed 64 KiB Uint16Array table that maps any two random bytes directly to two alphabet codes, halving the refill iteration count (endian-agnostic by construction).
  • Pooled CSPRNG: one crypto.getRandomValues() fill covers thousands of IDs at the default size instead of one syscall per ID.
  • Bitmask, no modulo: alphabet index comes from byte & 63 on a 64-character alphabet, so there is no rejection sampling or modulo bias on the hot path.
  • Precomputed tables for everything else: byte to hex for uuid()/uuidv7()/objectId(), char codes for Crockford in ulid()/monotonicFactory().

What nope-id does not try to beat:

  • Native crypto.randomUUID(): a C++ built-in, fastest for plain v4, not a JS library. If a v4 UUID is all you need, use it.
  • Smaller-alphabet generators like uid (16-char hex). A 21-char uid is ~84 bits; nope-id's 21 chars are ~126 bits, so at equal entropy nope-id is actually faster, but for short hex IDs uid is still a fine choice.

So nope-id's goal is to be the fastest while preserving maximum randomness per character, in one zero-dependency, dual-module package.

UUID generation vs the uuid package and native crypto.randomUUID()

A benchmark is only meaningful against more than one tool (thanks to nanoid's author for the nudge in #4). Here's the honest picture for UUIDs:

| Generator | ops/sec | | |---|---|---| | crypto.randomUUID() (Node native, v4) | ~21.4M | 🥇 fastest for plain v4 | | nope-id uuid() (v4) | ~6.2M | on par with @lukeed/uuid, ahead of uuid | | @lukeed/uuid v4() | ~6.6M | optimized pure-JS v4 | | uuid package v4() | ~5.7M | | | nope-id uuidv7() | ~4.0M | ~9x the uuid package's v7 | | uuid package v7() | ~452K | |

Honest take: if all you need is a random v4 UUID, Node's built-in crypto.randomUUID() is by far the fastest, so use it. nope-id doesn't try to beat native there. Its value is breadth: UUIDv7, ULID, Snowflake, ObjectId, Sqids, typed IDs and nanoid-style short IDs (most of which the uuid package and native don't offer), plus being faster than nanoid for URL-safe IDs and faster than the uuid package (especially v7), all dual-module and zero-dependency.

ULID (sortable) vs the ulid package

ULIDs are an attractive alternative to UUIDs: lexicographically sortable, a compact 26 characters (vs UUID's 36), Crockford base32 (URL-safe, case-insensitive), 128-bit and UUID-compatible, with a monotonic option that handles the same millisecond correctly. Unlike random UUID v4, their time prefix keeps database indexes from fragmenting.

nope-id ships a spec-compliant ulid() plus an isolated monotonicFactory(). Same 26-char format as the ulid package, both crypto-backed:

| Generator | ops/sec | |---|---| | nope-id ulid() | ~2.3M | | ulid package | ~30K | | nope-id monotonicFactory() | ~2.5M | | ulid package (monotonic) | ~2.3M |

nope-id is far faster for plain ulid() because it draws randomness from a pooled buffer (one fill per 16 IDs), whereas the ulid package fetches randomness per character. Decode the timestamp from either with decodeTime(). (The ulid package is also zero-dependency.)

Speed vs entropy: where each library lands

Two things matter for an id generator: speed and entropy, the amount of real randomness each id carries (its security against being guessed). These are all good libraries; the table shows the trade-off each one makes, measured at 21 characters where the length is configurable:

| Generator | ops/sec | entropy / id | randomness source | |---|---|---|---| | nope-id nopeid() | ~12.3M | ~126 bits (64-char URL-safe) | CSPRNG | | uid/secure | ~5.9M | ~84 bits (16-char hex) | CSPRNG | | nanoid | ~5.1M | ~126 bits (64-char URL-safe) | CSPRNG | | rndm | ~2.8M | ~125 bits, but predictable | Math.random (not secure) | | secure-random-string | ~392K | ~126 bits (base64, not URL-safe) | CSPRNG | | cuid2 createId() | ~5.7K | 24-char, hash-derived | CSPRNG + SHA-3 |

Read as two axes, speed and security, every other library gives something up on one of them:

  • uid/secure is fast, but pays with a smaller alphabet: ~84 bits per 21-char id against nope-id's ~126. For equal security you would have to make uid longer, at which point nope-id is already ahead per bit.
  • rndm is fast too, but it is built on Math.random, so its bits are predictable; its own README calls it "not cryptographically secure."
  • secure-random-string matches nope-id's entropy but is roughly 25x slower and emits base64 (not URL-safe).
  • cuid2 spends speed on purpose for a hardened, sharding-safe, hash-based model.
  • nanoid matches nope-id's entropy exactly (same 64-char alphabet); nope-id is simply ~2.8x faster at the default 21-char size.

nope-id is the one row that has all three at once: maximum entropy per character (126 bits), a real CSPRNG, and top-tier speed. That is the whole design goal, fast without ever spending randomness to get there. (For a plain v4 UUID, native crypto.randomUUID() is still faster at 122 bits in C++, so use it when a UUID is all you need.)

Extra Features Performance

These features are exclusive to nope-id (nanoid doesn't have them):

| Feature | Performance | |---------|-------------| | sortableId() | ~3.9M ops/sec | | prefixedId() | ~10.9M ops/sec | | uuid() | ~6.4M ops/sec | | slugId() | ~4.4M ops/sec | | shortId() | ~9.2M ops/sec | | isValid() | ~10.6M ops/sec | | uuidv7() | ~4.3M ops/sec | | ulid() | ~2.3M ops/sec | | monotonicFactory() | ~2.7M ops/sec | | snowflake (factory) | ~4.1M ops/sec | | objectId() | ~6.8M ops/sec | | sqids.encode() | ~202K ops/sec |

Why nope-id is Fast

  • Pre-translated Pool: nopeid() stores alphabet character codes in the pool (not raw bytes), so each call is a single Buffer.toString('latin1'), one V8 one-byte string allocation per ID instead of ~21 ConsString allocations from per-character concatenation
  • 16-bit Batch Refill: a 64 KiB Uint16Array table maps any two random bytes directly to two alphabet codes, halving the in-place translation loop (endian-agnostic by construction)
  • Optimized Pool Management: 65 KiB CSPRNG pool covers thousands of IDs per crypto.getRandomValues() call at the default 21-char size
  • Bitwise Operations: byte & 63 on a 64-character alphabet, no modulo and no rejection sampling on the hot path
  • Pre-computed Lookup Tables: Array access instead of string indexing for O(1) character lookup
  • Pre-cached Generators: Common functions like slugId() and shortId() use cached generators
  • Precomputed Hex & Crockford Tables: uuid()/uuidv7()/objectId()/ulid() format via byte to hex and char-code tables instead of per-byte conversion
  • Allocation-free customAlphabet: reads the shared byte pool directly (no per-call view allocation), which also speeds up slugId() and shortId()
  • Zero Dependencies: No external library overhead

Disclaimer

While nope-id uses cryptographically secure random number generators (crypto.getRandomValues) and implements security best practices, no software can guarantee 100% randomness or absolute security. The quality of randomness ultimately depends on the underlying operating system's entropy source.

For extremely high-security applications (e.g., cryptographic keys, long-term secrets), consider using dedicated cryptographic libraries that have undergone formal security audits.

nope-id is provided "as is" without warranty of any kind. Always evaluate whether it meets your specific security requirements.


License

MIT