
Security News
/Company News
Socket Is Sponsoring Composer and Packagist
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.
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
| uniku | uuid | typeid-js | nanoid | ulid | cuid2 | ksuid | bson | tsid-ts | |
|---|---|---|---|---|---|---|---|---|---|
| UUID v4 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| UUID v7 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| TypeID | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Nanoid | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| ULID | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| CUID2 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| KSUID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
| ObjectID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| TSID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| XID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 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@13is ESM-only; earlier versions support CommonJS.- ²
ulidonly provides timestamp encoding/decoding, not full binary serialization.
Uses globalThis.crypto (Web Crypto API) — no Node.js-specific APIs.
| Generator | uniku vs npm |
|---|---|
| ULID | 85× faster |
| CUID2 | 8× faster |
| KSUID | 1.5× faster |
| ObjectID | 1.1× faster |
| TSID | 1.7× faster |
| XID | 1.3× faster |
| UUID v7 | 1.1× faster |
| Nanoid | 1.9× faster |
| Nanoid (10 chars) | 1.3× faster |
| TypeID | 2.6× faster |
| UUID v4 | npm is 1.1× faster |
| Use Case | Recommended | Why |
|---|---|---|
| Database primary keys | UUID v7 or ULID | Time-ordered for index efficiency |
| API/domain identifiers | TypeID | UUID v7 with readable type prefixes like user_... |
| URL shorteners | Nanoid | Compact, URL-safe characters |
| Prevent enumeration | CUID2 | Non-sequential, secure |
| Maximum compatibility | UUID v4 | Universal standard |
| Distributed systems needing sortable, URL-safe IDs | ULID | Millisecond ordering + 80-bit entropy |
| Very high-volume distributed systems | KSUID | Time-ordered with 128-bit entropy |
MongoDB _id compatibility | ObjectID | Drop-in match for MongoDB's native document ID format |
| Native 64-bit sortable integer ID | TSID | Fits a database BIGINT primary key, no UUID-sized overhead |
| Go rs/xid compatibility | XID | Compact, time-ordered 12-byte identifier |
UUID v4 — Use when you need maximum compatibility with existing systems.
UUID type)UUID v7 — Use for database primary keys when you need RFC compliance.
UUID typeULID — Use for database primary keys when you want URL-safe IDs.
TypeID — Use when API IDs should expose their entity type.
user_01h2xcejqtf2nbrexx3vqjhp41KSUID — Use when you need highest collision resistance.
ObjectID — Use when you need drop-in compatibility with MongoDB.
_id fields, systems already speaking the ObjectID wire formatTSID — Use when you need a native 64-bit sortable integer ID, e.g. a database BIGINT primary key.
bigint by default (the only uniku generator that isn't string-primary); toString/fromString convert to/from the 13-character canonical stringBIGINT primary keys, high-throughput distributed ID generation without central coordinationNanoid — Use for short, URL-friendly identifiers.
CUID2 — Use when ID unpredictability matters.
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:
When to use binary:
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:
| Format | String Length | Binary Size | Savings |
|---|---|---|---|
| UUID v4/v7 | 36 chars | 16 bytes | 56% |
| ULID | 26 chars | 16 bytes | 38% |
| TypeID | prefix + 27 chars | 16 bytes | prefix-dependent |
| KSUID | 27 chars | 20 bytes | 26% |
| ObjectID | 24 chars | 12 bytes | 50% |
| TSID | 13 chars* | 8 bytes | 38% |
| Nanoid | 21 chars | N/A | - |
| CUID2 | 24 chars | N/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 canonicaltoString()form, shown here only for a size comparison against the other formats' native strings.
# pnpm (recommended)
pnpm add uniku
# bun
bun add uniku
# npm
npm install uniku
# yarn
yarn add uniku
# deno
deno install npm:uniku
| Import | Minified + 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.2 KB |
uniku/ksuid | ~1.3 KB |
uniku/objectid | ~1.3 KB |
uniku/tsid | ~1.4 KB |
uniku/xid | ~1.9 KB |
uniku/generators | ~98 B |
@noble/hashes; this table's entry-point size excludes that external dependency.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)
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
import { ulid } from 'uniku/ulid'
const id = ulid()
// => "01HW9T2W9W9YJ3JZ1H4P4M2T8Q"
// Convert to/from bytes
const bytes = ulid.toBytes(id)
const str = ulid.fromBytes(bytes)
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"
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/v2is the canonical entry point. The olderuniku/cuid2import still works and re-exports the same generator, but is now deprecated — preferimport { cuidv2 } from 'uniku/cuid/v2'.
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"
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)
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)
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)
import { xid } from 'uniku/xid'
const id = xid()
// => "9m4e2mr0ui3e8a215n4g"
const bytes = xid.toBytes(id)
const restored = xid.fromBytes(bytes)
const timestamp = xid.timestamp(id)
XID caches a random identity per runtime and uses a shared, always-incrementing counter. Supply machineId, processId, secs, and counter for deterministic output.
uuid- import { v4 as uuidv4 } from 'uuid'
+ import { uuidv4 } from 'uniku/uuid/v4'
nanoid- import { nanoid } from 'nanoid'
+ import { nanoid } from 'uniku/nanoid'
ulid- import { ulid } from 'ulid'
+ import { ulid } from 'uniku/ulid'
@paralleldrive/cuid2- import { createId } from '@paralleldrive/cuid2'
+ import { cuid2 } from 'uniku/cuid2'
@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)
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)
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
cuid2fromuniku/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()returnsbigintby default, andtoBytes/fromBytes/timestamp/isValidall operate on thatbigint.toString/fromStringare the boundary conversions to/from the 13-character canonical Crockford Base32 string.
xid (from uniku/xid)xid(options?: XidOptions): string
xid(options: XidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
xid.toBytes(id: string): Uint8Array
xid.fromBytes(bytes: Uint8Array): string
xid.timestamp(id: string): number
xid.isValid(id: unknown): id is string
xid.NIL // "00000000000000000000"
xid.MAX // "vvvvvvvvvvvvvvvvvvvg"
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:
Other:
Hi, I'm Alberto Schiabel, aka @jkomyno. You can follow me on:
MIT — see LICENSE
FAQs
Minimal, tree-shakeable unique ID generators for every JavaScript runtime
The npm package uniku receives a total of 16,312 weekly downloads. As such, uniku popularity was classified as popular.
We found that uniku demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
/Company News
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.

Research
/Security News
Benign-looking npm packages split malicious functionality across a dependency chain that deploys a cross-platform RAT targeting Alibaba developers.

Research
/Security News
Two Joyfill npm beta releases contain an import-time implant that uses blockchain transactions to retrieve a remote-access trojan.