scuid review
scuid 1.1.0 creates cuid-shaped identifiers from a prefix, base-36 timestamp, counter, process fingerprint, and two Math.random blocks. The normal result is 25 characters; slug() returns a shorter variable-width form. create() can change the prefix, radix, block length, hostname, or random source. Our package inspection found one 28 KB CommonJS module with no dependencies or types. The npm metadata still points to a GitHub repository that returns 404, so the published tarball is the only code available to audit. These values aim to avoid collisions, not resist guessing.
scuid 1.1.0 installed as one 1 MB package in 0.5 seconds, but our browser build failed, no types were present, and its default host fingerprint is constant because of a source bug. Keep it only for cuid-format compatibility with an explicit hostname; install cuid2, nanoid, ULID, or UUID for new IDs.
We installed it
| Install | ✓ · 0.5s | 1 package on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | n/a | could not be bundled for the browser (Node-only code, most likely) |
| Types | — | no TypeScript types found |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does scuid install cleanly?
Yes. In a fresh container with an empty cache, npm install scuid finished in 0.5s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.
Can scuid run in a browser?
Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.
Does scuid work with both ESM and CommonJS?
Yes. Both import 'scuid' and require('scuid') worked in Node 22 in our run. The package is published as CommonJS.
Does scuid include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
scuid or @paralleldrive/cuid2: which should you use?
@paralleldrive/cuid2: Use it for the successor recommended by deprecated cuid, with cryptographic randomness and no exposed timestamp prefix. scuid 1.1.0 installed as one 1 MB package in 0.5 seconds, but our browser build failed, no types were present, and its default host fingerprint is constant because of a source bug.
When should you not use scuid?
The identifier protects a secret or access path. Math.random plus exposed time, counter, and fingerprint components makes output predictable enough to reject for tokens.
Use it if
- Existing records and APIs already require the original cuid string layout.
- A timestamp prefix should make later full identifiers generally sort after earlier ones as strings.
- A legacy caller needs create({ rng }) to inject an object with a random() method.
- You are preserving a transitive dependency and can audit the complete 28 KB published package yourself.
- The identifier protects a secret or access path. Math.random plus exposed time, counter, and fingerprint components makes output predictable enough to reject for tokens.
- Machine-specific fingerprints are expected from defaults. The code stores os.hostname itself instead of calling it, which leaves the host block at the constant `10`.
- A public issue tracker and commit history are required. The repository URL in npm currently returns 404, leaving no visible development record.
- Short values must have fixed width. slug() grows from 7 to 10 characters as its counter representation expands.
- This is a new identifier design. The original cuid package is deprecated, warns that similar sortable IDs are insecure, and recommends cuid2.
- Native ESM, bundled TypeScript declarations, or active releases are mandatory. Version 1.1.0 is untyped CommonJS published in February 2018.
Setup reality
We installed scuid 1.1.0 in 0.5 seconds in a fresh Node 22 Bookworm container. It left 1 package and 1 MB on disk, and npm audit found 0 known vulnerabilities. The package has no direct or peer dependencies, is 28 KB unpacked, and uses the MIT license. It is CommonJS without an exports map. require() and ESM import both succeeded in our checks, but no TypeScript declarations were found. An esbuild browser bundle failed, which matches its dependency on Node process and OS details.
There are no credentials, native builds, or config files. The published artifact contains package metadata, a license, a README, and lib/scuid.js. Its linked repository returns 404, so production review has to start with that installed source. The default export is a shared generator; create() returns a configured instance, and the class itself is exposed under the unusual constructor property.
The most important default is faulty. hostname is assigned the os.hostname function rather than os.hostname(). Fingerprint code then reads that function's zero argument length and generates the same trailing 10 host block everywhere. Pass create({ hostname: os.hostname() }) if compatibility forces you to keep scuid, although that changes the fingerprint portion compared with the broken default.
Full IDs are 25 characters, while slug output ranges from 7 to 10 as the counter grows. The counter wraps after 36^4 values, and the random blocks use Math.random. Do not use either form as an API key, reset token, invitation secret, or proof of authorization. For new database identifiers, choose cuid2, nanoid, ULID, or UUID according to whether randomness, ordering, or a standard format matters.
Patterns
Create one full identifier generate-an-id
const scuid = require('scuid')
const id = scuid()
console.log(id) // 'cmskv45910000ef10v34xxchc'
console.log(id.length) // 25Output is 25 characters: `c`, 8 timestamp characters, 4 counter characters, a 4-character fingerprint, and two random blocks of 4.
Correct the default hostname input fix-the-fingerprint
const os = require('node:os')
const scuid = require('scuid')
console.log(scuid.fingerprint()) // 'ef10' - host block is always '10'
const generator = scuid.create({ hostname: os.hostname() })
console.log(generator.fingerprint()) // 'efjx' - host block now variesVersion 1.1.0 stores the os.hostname function itself, producing a constant host block. Calling os.hostname() before create() supplies the intended string.
Generate the shorter slug form generate-a-slug
const scuid = require('scuid')
const slugs = Array.from({ length: 5 }, () => scuid.slug())
console.log(slugs)
console.log(new Set(slugs.map((s) => s.length))) // widths varySlug width grows from 7 through 10 characters with the counter. Database and API validation must allow that range.
Inject a random-number provider custom-rng
const crypto = require('node:crypto')
const scuid = require('scuid')
const secure = scuid.create({
rng: { random: () => crypto.randomInt(0, 2 ** 30) / 2 ** 30 },
})
console.log(secure.id())A custom RNG changes two blocks only. Time, counter, and fingerprint data remain exposed, so the complete value is unsuitable as a secret.
Configure a separate generator configured-instance
const os = require('node:os')
const scuid = require('scuid')
const ids = scuid.create({
prefix: 'u',
hostname: os.hostname(),
pid: process.pid,
})
console.log(ids.id(), ids.slug(), ids.fingerprint())The README warns that base, blockSize, and fill changes break cuid compatibility. Prefix and hostname can change without altering field widths.
Sort identifiers by their prefix sort-by-creation
const scuid = require('scuid')
const rows = [scuid(), scuid(), scuid()].map((id) => ({ id }))
rows.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
// string sort matches creation order within one processOrdering depends on system clocks. Multiple machines can interleave values, and a backward clock correction can reverse expected creation order.
Validate the full string shape validate-format
const ID_RE = /^c[0-9a-z]{24}$/
function isScuid (value) {
return typeof value === 'string' && ID_RE.test(value)
}scuid provides no parser or validator. A regular expression can confirm syntax but cannot prove that your application issued the value.
Account for counter rollover counter-wraps
const scuid = require('scuid')
const instance = scuid.create({})
console.log(instance.discreteValues) // 1679616 = 36 ** 4
// the counter cycles through that range, then restarts at 0The counter range is 36^4. After it wraps within the same timestamp, collision resistance rests on the two Math.random blocks.
Inspect an explicit fingerprint per-process-fingerprint
const scuid = require('scuid')
console.log(scuid.fingerprint())
console.log(scuid.createFingerprint(process.pid, require('node:os').hostname()))createFingerprint() with a hostname string varies by host. The singleton fingerprint does not because its default contains the uncalled function.
Review the installed implementation read-the-source
npm pack scuid
tar tzf scuid-1.1.0.tgz
# package/package.json
# package/LICENSE.md
# package/README.md
# package/lib/scuid.jsThe npm artifact is the available source of record because its repository link returns 404. Inspect lib/scuid.js from the installed 28 KB package.
Reject scuid for secret values avoid-for-secrets
const crypto = require('node:crypto')
// wrong: predictable timestamp, counter and fingerprint
// const resetToken = require('scuid')()
const resetToken = crypto.randomBytes(32).toString('base64url')Upstream cuid's npm notice calls its sortable design insecure and recommends cuid2. scuid repeats the predictable components that motivate that warning.
Introduce cuid2 for new rows migrate-to-cuid2
const { createId, isCuid } = require('@paralleldrive/cuid2')
const id = createId()
console.log(isCuid(id))
// keep scuid only for reading rows that already exist
// const legacy = require('scuid')cuid2 uses a different string format, so migrations must accept old and new identifiers together. It also drops time sorting to avoid a predictable prefix.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @paralleldrive/cuid2 | npm | Use it for the successor recommended by deprecated cuid, with cryptographic randomness and no exposed timestamp prefix. |
| nanoid | npm | Use it for short URL-safe random IDs when creation-time ordering is unnecessary. |
| ulid | npm | Use it for a documented sortable format with a millisecond timestamp that can be decoded. |
| uuid | npm | Use it when interoperability matters, including UUIDv7 for time-ordered database keys. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

