New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

uniku

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

uniku

Minimal, tree-shakeable unique ID generators for every JavaScript runtime

Source
npmnpm
Version
0.3.0
Version published
Weekly downloads
171K
-30.71%
Maintainers
1
Weekly downloads
 
Created
Source

uniku

npm version npm downloads Bundle Size CI TypeScript License: MIT

One library. Every ID format. Every runtime. One runtime dependency (@noble/hashes, CUID2 only).

uniku /uˈniːku/ — Maltese for "unique"

import { uuidv7 } from 'uniku/uuid/v7'

const id = uuidv7()
// => "018e5e5c-7c8a-7000-8000-000000000000"

// Time-ordered: IDs sort by creation time
const [first, second, third] = [uuidv7(), uuidv7(), uuidv7()]
console.log(first < second && second < third) // true

At a Glance

unikuuuidtypeid-jsnanoidulidcuid2ksuidbsontsid-ts
UUID v4
UUID v7
TypeID
Nanoid
ULID
CUID2
KSUID
ObjectID
TSID
Tree-shakeable
ESM-only✅¹
Edge/Workers⚠️⚠️⚠️⚠️
Byte ↔ String-⚠️²-

Notes:

  • Byte ↔ String conversion doesn't make sense for nanoid and cuid2, since they are string-native formats with no canonical binary representation.
  • ¹ uuid@13 is ESM-only; earlier versions support CommonJS.
  • ² ulid only provides timestamp encoding/decoding, not full binary serialization.

Works Everywhere

Node.js Deno Bun Cloudflare Workers Vercel Edge Browsers

Uses globalThis.crypto (Web Crypto API) — no Node.js-specific APIs.

Performance

Generatoruniku vs npm
ULID85× faster
CUID28× faster
KSUID1.5× faster
ObjectID1.1× faster
TSID1.7× faster
UUID v71.1× faster
Nanoid~comparable speed
Nanoid (10 chars)npm is 1.1× faster
TypeID2.6× faster
UUID v4npm is 1.1× faster

Which ID Should I Use?

Quick Recommendations

Use CaseRecommendedWhy
Database primary keysUUID v7 or ULIDTime-ordered for index efficiency
API/domain identifiersTypeIDUUID v7 with readable type prefixes like user_...
URL shortenersNanoidCompact, URL-safe characters
Prevent enumerationCUID2Non-sequential, secure
Maximum compatibilityUUID v4Universal standard
Distributed systems needing sortable, URL-safe IDsULIDMillisecond ordering + 80-bit entropy
Very high-volume distributed systemsKSUIDTime-ordered with 128-bit entropy
MongoDB _id compatibilityObjectIDDrop-in match for MongoDB's native document ID format
Native 64-bit sortable integer IDTSIDFits a database BIGINT primary key, no UUID-sized overhead

Detailed Guide

UUID v4 — Use when you need maximum compatibility with existing systems.

  • 36 characters, 122-bit entropy
  • Universally supported by databases (native UUID type)
  • Not time-ordered (random distribution)
  • Best for: Legacy systems, cross-platform compatibility

UUID v7 — Use for database primary keys when you need RFC compliance.

  • 36 characters, time-ordered with millisecond precision
  • Native database support as UUID type
  • Strictly monotonic within same millisecond
  • Best for: PostgreSQL/MySQL primary keys, API identifiers

ULID — Use for database primary keys when you want URL-safe IDs.

  • 26 characters, time-ordered with millisecond precision
  • URL-safe (no hyphens), case-insensitive
  • 80-bit random entropy per millisecond
  • Best for: APIs, URLs, distributed systems needing sortability

TypeID — Use when API IDs should expose their entity type.

  • Prefix plus 26-character UUID v7 suffix, e.g. user_01h2xcejqtf2nbrexx3vqjhp41
  • Empty prefix support for canonical 26-character TypeIDs
  • Time-ordered through UUID v7, with lowercase snake_case type prefixes
  • Best for: Public API identifiers, logs, debugging, domain-specific IDs

KSUID — Use when you need highest collision resistance.

  • 27 characters, time-ordered with second precision
  • URL-safe, 128-bit random entropy (highest of all formats)
  • Best for: Very high-volume distributed systems, event sourcing

ObjectID — Use when you need drop-in compatibility with MongoDB.

  • 24-character lowercase hex, time-ordered with second precision
  • 4-byte timestamp + 5-byte per-process random + 3-byte always-incrementing counter
  • Counter never resets on a new timestamp (unlike ULID/UUIDv7/KSUID sequences)
  • Best for: MongoDB _id fields, systems already speaking the ObjectID wire format

TSID — Use when you need a native 64-bit sortable integer ID, e.g. a database BIGINT primary key.

  • 64-bit Snowflake-style value, time-ordered with millisecond precision
  • Returns a bigint by default (the only uniku generator that isn't string-primary); toString/fromString convert to/from the 13-character canonical string
  • 42-bit timestamp + 10-bit node + 12-bit per-millisecond counter, with a persistent lazily-random node ID
  • Best for: relational database BIGINT primary keys, high-throughput distributed ID generation without central coordination

Nanoid — Use for short, URL-friendly identifiers.

  • 21 characters (configurable), 126-bit entropy
  • Customizable alphabet and length
  • Not time-ordered
  • Best for: URL shorteners, session tokens, invite codes

CUID2 — Use when ID unpredictability matters.

  • 24 characters (configurable), hash-based
  • Non-sequential, prevents enumeration attacks
  • Not time-ordered
  • Best for: User-facing IDs where security matters

Binary Serialization: When to Use toBytes()

Formats with binary representations (UUID, ULID, KSUID, ObjectID, TSID) support toBytes() and fromBytes() for efficient storage:

// Store as BINARY(16) instead of VARCHAR(36)
const bytes = uuidv7.toBytes(id)  // 16 bytes
db.insert({ id: bytes })

// Retrieve and convert back
const id = uuidv7.fromBytes(row.id)

Benefits of binary storage:

  • Up to 50% smaller: E.g., 16 bytes vs 36 characters for UUID
  • Faster indexing: Binary comparison is faster than string comparison
  • Reduced I/O: Less data transferred between app and database

When to use binary:

  • High-volume tables (millions of rows)
  • Performance-critical queries on ID columns
  • Storage cost is a concern

Exception — tsid: toBytes()/fromBytes() operate on bigint, not string, since bigint is TSID's primary type (see tsid in the API Reference below). Every other generator's toBytes()/fromBytes() operate on its primary string type.

When to keep strings:

  • Debugging/logging convenience
  • API responses (humans read them)
  • Small tables where savings are negligible
FormatString LengthBinary SizeSavings
UUID v4/v736 chars16 bytes56%
ULID26 chars16 bytes38%
TypeIDprefix + 27 chars16 bytesprefix-dependent
KSUID27 chars20 bytes26%
ObjectID24 chars12 bytes50%
TSID13 chars*8 bytes38%
Nanoid21 charsN/A-
CUID224 charsN/A-

Note: Nanoid and CUID2 don't have binary representations because they're string-native formats with no canonical byte encoding.

* TSID's primary type is bigint, not a string; the 13-character figure is its canonical toString() form, shown here only for a size comparison against the other formats' native strings.

Installation

# pnpm (recommended)
pnpm add uniku

# bun
bun add uniku

# npm
npm install uniku

# yarn
yarn add uniku

# deno
deno install npm:uniku

Bundle Impact

ImportMinified + gzipped
uniku/uuid/v4~1.1 KB
uniku/uuid/v7~1.4 KB
uniku/ulid~1.8 KB
uniku/typeid~1.6 KB
uniku/cuid/v2~992 B*
uniku/cuid2~1007 B*
uniku/nanoid~1.1 KB
uniku/ksuid~1.3 KB
uniku/objectid~1.3 KB
uniku/tsid~1.4 KB
uniku/generators~98 B
  • The CUID2 entry point imports SHA3-512 from @noble/hashes; this table's entry-point size excludes that external dependency.

Quick Start

UUID v4 (random)

import { uuidv4 } from 'uniku/uuid/v4'

const id = uuidv4()
// => "550e8400-e29b-41d4-a716-446655440000"

// Convert to/from bytes
const bytes = uuidv4.toBytes(id)
const str = uuidv4.fromBytes(bytes)

UUID v7 (time-ordered)

import { uuidv7 } from 'uniku/uuid/v7'

const id = uuidv7()
// => "018e5e5c-7c8a-7000-8000-000000000000"

// IDs are lexicographically sortable by creation time
const ids = [uuidv7(), uuidv7(), uuidv7()]
ids.sort() // Already in creation order

ULID (time-ordered)

import { ulid } from 'uniku/ulid'

const id = ulid()
// => "01HW9T2W9W9YJ3JZ1H4P4M2T8Q"

// Convert to/from bytes
const bytes = ulid.toBytes(id)
const str = ulid.fromBytes(bytes)

TypeID (prefixed UUID v7)

import { typeid } from 'uniku/typeid'

const id = typeid('user')
// => "user_01h2xcejqtf2nbrexx3vqjhp41"

const uuid = typeid.toUuid(id)
const restored = typeid.fromUuid('user', uuid)

const canonical = typeid('')
// => "01h2xcejqtf2nbrexx3vqjhp41"

CUID2 (secure, non-time-ordered)

import { cuidv2 } from 'uniku/cuid/v2'

const id = cuidv2()
// => "pfh0haxfpzowht3oi213cqos"

// Custom length
const shortId = cuidv2({ length: 10 })
// => "tz4a98xxat"

// Validation (type guard)
if (cuidv2.isValid(maybeId)) {
  console.log(maybeId) // TypeScript knows this is a string
}

uniku/cuid/v2 is the canonical entry point. The older uniku/cuid2 import still works and re-exports the same generator, but is now deprecated — prefer import { cuidv2 } from 'uniku/cuid/v2'.

Nanoid (URL-friendly, custom alphabet)

import { nanoid } from 'uniku/nanoid'

const id = nanoid()
// => "V1StGXR8_Z5jdHi6B-myT"

// Custom size
const shortId = nanoid(10)
// => "IRFa-VaY2b"

// Custom alphabet and size via options
const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })
// => "4f90d13a42bc"

KSUID (time-ordered, high entropy)

import { ksuid } from 'uniku/ksuid'

const id = ksuid()
// => "2QnJjKLvpSfpZqGiPPxVwWLMy2p"

// Convert to/from bytes
const bytes = ksuid.toBytes(id)
const str = ksuid.fromBytes(bytes)

// Extract timestamp (milliseconds)
const ts = ksuid.timestamp(id)

ObjectID (time-ordered, MongoDB-compatible)

import { objectid } from 'uniku/objectid'

const id = objectid()
// => "667c3f2a1e2b3c4d5e6f7081"

// Convert to/from bytes
const bytes = objectid.toBytes(id)
const str = objectid.fromBytes(bytes)

// Extract timestamp (milliseconds)
const ts = objectid.timestamp(id)

TSID (time-ordered, 64-bit integer)

import { tsid } from 'uniku/tsid'

// Generate a TSID bigint (the primary type, not a string)
const id = tsid()
// => 862301223059968074n

// Convert to/from the canonical 13-character string
const str = tsid.toString(id)
// => "0QXW2CK4XZM2A"
tsid.fromString(str) === id // true

// Extract timestamp (milliseconds, full precision)
const ts = tsid.timestamp(id)

// Convert to/from 8 bytes
const bytes = tsid.toBytes(id)
const restored = tsid.fromBytes(bytes)

Migrating to uniku

From uuid

- import { v4 as uuidv4 } from 'uuid'
+ import { uuidv4 } from 'uniku/uuid/v4'

From nanoid

- import { nanoid } from 'nanoid'
+ import { nanoid } from 'uniku/nanoid'

From ulid

- import { ulid } from 'ulid'
+ import { ulid } from 'uniku/ulid'

From @paralleldrive/cuid2

- import { createId } from '@paralleldrive/cuid2'
+ import { cuid2 } from 'uniku/cuid2'

From @owpz/ksuid

- import { KSUID } from '@owpz/ksuid'
+ import { ksuid } from 'uniku/ksuid'

- const id = KSUID.random().toString()
+ const id = ksuid()

- const parsed = KSUID.parse(str)
- const timestamp = parsed.timestamp
+ const timestamp = ksuid.timestamp(str)

From bson

- import { ObjectId } from 'bson'
+ import { objectid } from 'uniku/objectid'

- const id = new ObjectId().toHexString()
+ const id = objectid()

- const timestamp = new ObjectId(str).getTimestamp().getTime()
+ const timestamp = objectid.timestamp(str)

API Reference

uuidv4 (from uniku/uuid/v4)

uuidv4(options?: UuidV4Options): string
uuidv4(options: UuidV4Options | undefined, buf: Uint8Array, offset?: number): Uint8Array
uuidv4.toBytes(id: string): Uint8Array
uuidv4.fromBytes(bytes: Uint8Array): string
uuidv4.isValid(id: unknown): id is string
uuidv4.NIL  // "00000000-0000-0000-0000-000000000000"
uuidv4.MAX  // "ffffffff-ffff-ffff-ffff-ffffffffffff"

uuidv7 (from uniku/uuid/v7)

uuidv7(options?: UuidV7Options): string
uuidv7(options: UuidV7Options | undefined, buf: Uint8Array, offset?: number): Uint8Array
uuidv7.toBytes(id: string): Uint8Array
uuidv7.fromBytes(bytes: Uint8Array): string
uuidv7.timestamp(id: string): number
uuidv7.isValid(id: unknown): id is string
uuidv7.NIL  // "00000000-0000-0000-0000-000000000000"
uuidv7.MAX  // "ffffffff-ffff-ffff-ffff-ffffffffffff"

ulid (from uniku/ulid)

ulid(options?: UlidOptions): string
ulid(options: UlidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
ulid.toBytes(id: string): Uint8Array
ulid.fromBytes(bytes: Uint8Array): string
ulid.timestamp(id: string): number
ulid.isValid(id: unknown): id is string
ulid.NIL  // "00000000000000000000000000"
ulid.MAX  // "7ZZZZZZZZZZZZZZZZZZZZZZZZZ"

typeid (from uniku/typeid)

typeid(prefix: string, options?: TypeidOptions): string
typeid.toBytes(id: string): Uint8Array
typeid.fromBytes(prefix: string, bytes: Uint8Array): string
typeid.toUuid(id: string): string
typeid.fromUuid(prefix: string, uuid: string): string
typeid.timestamp(id: string): number
typeid.prefix(id: string): string
typeid.suffix(id: string): string
typeid.isValid(id: unknown): id is string

Use an empty prefix (typeid('')) to generate Jetify-compatible prefixless TypeIDs.

cuidv2 (from uniku/cuid/v2)

cuidv2(options?: CuidV2Options): string
cuidv2.isValid(id: unknown): id is string

Also available (deprecated) as cuid2 from uniku/cuid2 — the same generator under the pre-versioned entry point.

nanoid (from uniku/nanoid)

nanoid(): string
nanoid(size: number): string
nanoid(options: NanoidOptions): string
nanoid.isValid(id: unknown): id is string

// Constant
URL_ALPHABET  // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"

ksuid (from uniku/ksuid)

ksuid(options?: KsuidOptions): string
ksuid(options: KsuidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
ksuid.toBytes(id: string): Uint8Array
ksuid.fromBytes(bytes: Uint8Array): string
ksuid.timestamp(id: string): number
ksuid.isValid(id: unknown): id is string
ksuid.NIL  // "000000000000000000000000000"
ksuid.MAX  // "aWgEPTl1tmebfsQzFP4bxwgy80V"

objectid (from uniku/objectid)

objectid(options?: ObjectIdOptions): string
objectid(options: ObjectIdOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
objectid.toBytes(id: string): Uint8Array
objectid.fromBytes(bytes: Uint8Array): string
objectid.timestamp(id: string): number
objectid.isValid(id: unknown): id is string
objectid.NIL  // "000000000000000000000000"
objectid.MAX  // "ffffffffffffffffffffffff"

tsid (from uniku/tsid)

tsid(options?: TsidOptions): bigint
tsid(options: TsidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
tsid.toBytes(id: bigint): Uint8Array
tsid.fromBytes(bytes: Uint8Array): bigint
tsid.toString(id: bigint): string
tsid.fromString(str: string): bigint
tsid.timestamp(id: bigint, epoch?: number): number
tsid.isValid(id: unknown): id is bigint
tsid.NIL  // 0n
tsid.MAX  // 18446744073709551615n

Unlike every other generator listed here, tsid() returns bigint by default, and toBytes/fromBytes/timestamp/isValid all operate on that bigint. toString/fromString are the boundary conversions to/from the 13-character canonical Crockford Base32 string.

Documentation

For advanced usage, examples, and contributing guidelines, see the full documentation on GitHub. The uniku@1.x stability contract defines the supported entry points, runtimes, and release gates.

Third-party libraries that inspired this project:

  • uuid: the most popular UUID library for JavaScript
  • ulid: the reference ULID implementation for JavaScript
  • TypeID-JS: official JavaScript implementation of TypeID
  • @paralleldrive/cuid2: secure, collision-resistant IDs
  • @owpz/ksuid: K-Sortable Unique Identifier
  • bson: official MongoDB BSON library, including the ObjectId implementation
  • nanoid: tiny, URL-friendly unique string ID generator
  • tsid-ts: TypeScript implementation of TSID, a Snowflake-style 64-bit Time-Sorted Unique Identifier

Other:

Author

Hi, I'm Alberto Schiabel, aka @jkomyno. You can follow me on:

License

MIT — see LICENSE

Keywords

uuid

FAQs

Package last updated on 10 Jul 2026

Related posts