scuid
scuid generates collision-resistant string identifiers in the cuid format: a 'c' prefix, a base-36 timestamp, a rolling counter, a per-process fingerprint, and two random blocks, giving a 25-character id such as cmskv45910000ef10v34xxchc. It also produces short slugs and exposes the fingerprint separately, and lets you build a custom generator with a different prefix, radix, block size or random number generator. It was written as a slimmer, faster drop-in for the original cuid package. The whole implementation is a single 150-line file with no dependencies, which is worth knowing because that file is now the only documentation of record.
A clean, tiny cuid implementation with a real defect in its default fingerprint and no public repository behind 4.2 million weekly downloads. Keep it only to match existing cuid-shaped data, pass an explicit hostname if you do, and reach for cuid2, nanoid or ulid for anything new.
Use it if
- You have existing data in cuid format and need a generator that produces the same shape without adding the original cuid package
- You want a sortable-by-prefix id where the leading characters are a base-36 timestamp, so ids created later sort after ids created earlier as strings
- You need to swap in your own random number generator, which the create({ rng }) option supports with any object exposing a random method
- You are auditing a dependency tree and need to know exactly what this package does, since it is small enough to read end to end
- You need ids to be unguessable, because the default generator is Math.random and the timestamp, counter and fingerprint parts are all predictable; this is a collision-avoidance tool, not a security token
- You expect the fingerprint to distinguish machines: the default hostname value is the os.hostname function itself rather than a string, so the host block is a constant '10' on every host, verified by running 1.1.0
- You want a public source of truth, because the repository listed on npm returns 404 and the author's GitHub account shows zero public repositories, so there is no issue tracker, no test suite to read, and no history
- You need fixed-width slugs, since slug() ranges from seven to ten characters depending on the counter value, while the README shows only a seven-character example
- You are starting fresh, since the original cuid it copies is itself deprecated on npm with a notice saying cuid and similar k-sortable ids are insecure and recommending cuid2 instead
- You want maintenance, TypeScript declarations or ESM, because 1.1.0 shipped in February 2018 and is a CommonJS file with no types and no releases since
Setup reality
npm install scuid, then require it and call it: no dependencies, no build step, no configuration. The package contains four files, package.json, LICENSE.md, README.md and lib/scuid.js, and that is the complete artefact. There is no repository to look at, so reading lib/scuid.js is not optional if you care how the ids are formed, and doing that surfaces the thing worth knowing before you deploy it. The default options set hostname to os.hostname, the function object, instead of calling it. That value flows into the fingerprint routine, which computes a host block from the argument's length and character codes; a function has a length of zero and no characters to iterate, so every host produces the same block. Running 1.1.0 shows a fingerprint of 'ef10' where the trailing '10' is constant everywhere and the leading pair is only the last two base-36 digits of the process id. The README's own sample output, 'io10', ends the same way. The fix is one option: pass create({ hostname: os.hostname() }) and the host block becomes machine-specific again, which the same run confirms. Nothing else about setup is surprising. Ids are 25 characters, slugs vary between seven and ten characters as the counter grows, the counter wraps at 36 to the fourth power, and 200,000 consecutive ids and slugs were unique in a single process during a check of this version. The module exports a singleton for the common case plus create for a configured instance and constructor for the class itself, which is an unusual place to hang it and easy to miss. Treat the deprecation notice on the upstream cuid package as applying here too: it says these ids are not secure, and this implementation gives you no reason to disagree.
Patterns
Get a cuid-format identifiergenerate-an-id
const scuid = require('scuid')
const id = scuid()
console.log(id) // 'cmskv45910000ef10v34xxchc'
console.log(id.length) // 25Always 25 characters: 'c', an 8-character base-36 timestamp, a 4-character counter, a 4-character fingerprint and two 4-character random blocks.
Pass a real hostname so the fingerprint varies by machinefix-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 variesThe default leaves hostname as the os.hostname function rather than its result, so the host block degrades to a constant. Verified on 1.1.0.
Get a short id and account for its widthgenerate-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 varySlugs are seven to ten characters, widening as the counter grows. Do not declare a fixed-width column or a fixed-length validation rule for them.
Supply your own random sourcecustom-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())This hardens only the two random blocks. The timestamp, counter and fingerprint remain predictable, so the id as a whole is still not a secret.
Build a generator with your own settingsconfigured-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 changing base, blockSize or fill makes ids incompatible with cuid's guarantees. Changing only prefix and hostname keeps the shape intact.
Rely on the timestamp prefix for orderingsort-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 holds only as far as clock resolution and clock stability allow. Ids from different machines interleave, and an NTP step backwards breaks the ordering.
Check that a string looks like one of these idsvalidate-format
const ID_RE = /^c[0-9a-z]{24}$/
function isScuid (value) {
return typeof value === 'string' && ID_RE.test(value)
}The package ships no validator, so this is yours to write. It confirms the shape only; it says nothing about whether the id was ever issued by you.
Know where the counter resetscounter-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 0Uniqueness after a wrap depends on the timestamp and the random blocks. A single process generating more than 1.6 million ids inside one millisecond is the theoretical failure point.
Read the fingerprint for logging or shardingper-process-fingerprint
const scuid = require('scuid')
console.log(scuid.fingerprint())
console.log(scuid.createFingerprint(process.pid, require('node:os').hostname()))createFingerprint with an explicit hostname string gives the machine-specific value; the singleton's fingerprint() does not, because of the default. The two disagree by design of the bug, not by design.
Audit the package from the tarballread-the-source
npm pack scuid
tar tzf scuid-1.1.0.tgz
# package/package.json
# package/LICENSE.md
# package/README.md
# package/lib/scuid.jsFour files, one of them the implementation. There is no GitHub repository to review, so the tarball is the only way to see what you are running.
Do not use these where guessing mattersavoid-for-secrets
const crypto = require('node:crypto')
// wrong: predictable timestamp, counter and fingerprint
// const resetToken = require('scuid')()
const resetToken = crypto.randomBytes(32).toString('base64url')The npm deprecation notice on the upstream cuid package says these ids are insecure and points at cuid2. The same reasoning applies to this reimplementation.
Move new records to a maintained generatormigrate-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')The formats are different, so plan for both to coexist. cuid2 ids are not time-sortable, which is the property you give up in exchange for unpredictability.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @paralleldrive/cuid2 | npm | You want the successor the deprecated cuid package points at, with cryptographic randomness and no predictable timestamp prefix |
| nanoid | npm | You want short, URL-safe ids from a cryptographically secure source and do not need them to sort by creation time |
| ulid | npm | You want lexicographically sortable ids like these but with a documented specification and a millisecond timestamp you can decode back |
| uuid | npm | You want the standard everything already understands, including v7 when you need time ordering in a database index |