mrkeyoor.com_
Sat 08 Aug 22:50 UTC
npmUtilsupdated 08 Aug 2026

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.

Verdict

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.

API stability4/5Three published versions in total and no change since 1.1.0 in February 2018, so the callable module, slug, fingerprint, createFingerprint, create and constructor exports are effectively permanent. The id format has not moved either, which matters when ids are already in a database. The point off is for the constructor export, an unusual name for the class that no other library in this space uses, and for the fact that fixing the hostname default would technically change every generated fingerprint, so it never can be fixed in place.
Docs2/5The README is clear about the common calls and shows the options object with a short line for each field, plus a benchmark table comparing it against cuid. It is also the only documentation, since the repository it links to is gone, and it is wrong or misleading in two places: the slug example shows a seven-character value when the real width varies between seven and ten, and the options list presents hostname as if the default resolves to a host name when it is left as an uncalled function. The test and benchmark instructions refer to scripts that are not in the published package.
Maintenance1/5The last publish was 1.1.0 on 2018-02-04. The repository named in package.json, jhermsmeier/node-scuid, returns 404, and the author's GitHub account reports zero public repositories, so there is no issue tracker, no commit history and no test suite available for review. That means a bug found today has nowhere to be reported and no realistic path to a fix. For a package installed 4.2 million times a week, being unauditable outside the npm tarball is the headline maintenance fact.
Ecosystem2/5Around 4.2 million weekly downloads, largely transitive through older GraphQL and scaffolding tooling that wanted a cuid generator without the original package. Nothing is built on top of it: no TypeScript declarations, no ESM build, no browser bundle, no framework integrations, no validator or parser for the format. The format itself is shared with cuid, so ids are interchangeable there, but cuid is deprecated on npm and its ecosystem has moved to cuid2, which uses a different and incompatible format.

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
Skip it if

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)   // 25

Always 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 varies

The 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 vary

Slugs 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 process

Ordering 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 0

Uniqueness 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.js

Four 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

PackageRegistryPick it when
@paralleldrive/cuid2npmYou want the successor the deprecated cuid package points at, with cryptographic randomness and no predictable timestamp prefix
nanoidnpmYou want short, URL-safe ids from a cryptographically secure source and do not need them to sort by creation time
ulidnpmYou want lexicographically sortable ids like these but with a documented specification and a millisecond timestamp you can decode back
uuidnpmYou want the standard everything already understands, including v7 when you need time ordering in a database index