This comparison evaluates six popular libraries for generating unique IDs in JavaScript environments. Each library offers a different trade-off between ID length, collision resistance, sortability, and security. Developers must choose based on whether IDs need to be cryptographically secure, sortable by time, or optimized for minimal bundle size. Some options are deprecated and should be avoided in new projects.
Choosing the right ID generation library affects database performance, security, and code maintainability. While all six packages create unique strings, they differ significantly in how they generate entropy, handle sorting, and manage bundle weight. Let's compare how they tackle common engineering problems.
nanoid uses cryptographically strong random values generated by the Web Crypto API in browsers. It is designed to be secure against prediction.
// nanoid: Secure random generation
import { nanoid } from 'nanoid';
const id = nanoid(); // e.g., "V1StGXR8_Z5jdHi6B-myT"
uuid (specifically v4) also uses random numbers but follows the RFC4122 standard strictly. It is secure but verbose.
// uuid: RFC4122 v4 random
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4(); // e.g., "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
shortid relied on a random algorithm that is no longer considered secure enough for high-volume systems. It is deprecated.
// shortid: Deprecated random generation
import shortid from 'shortid';
const id = shortid(); // e.g., "P3ckZHu"
cuid uses fingerprints and counters, which led to collision reports in clustered environments. It is not recommended for security-critical paths.
// cuid: Fingerprint-based (Legacy)
import cuid from 'cuid';
const id = cuid(); // e.g., "cjld2cjxh0000qzrmn831i7rn"
ulid combines a timestamp with random entropy. It is secure enough for most apps but prioritizes sortability over pure randomness.
// ulid: Time-based + random
import { ulid } from 'ulid';
const id = ulid(); // e.g., "01ARZ3NDEKTSV4RRFFQ69G5FAV"
ksuid similar to ULID, it mixes time and randomness but follows a different encoding scheme. It is secure but less common in JS.
// ksuid: Time + random (K-Sortable)
import KSUID from 'ksuid';
const id = KSUID.new().toString(); // e.g., "1vOz25H9z3X4y5W6v7U8t9S0"
ulid embeds the timestamp in the first part of the ID. This allows databases to index records chronologically without a separate created_at column.
// ulid: Lexicographically sortable
const id1 = ulid(); // Generated at T1
const id2 = ulid(); // Generated at T2
// id1 < id2 if T1 < T2
ksuid also embeds time, making it sortable. It is designed to be globally unique and ordered by creation time.
// ksuid: Sortable by timestamp
const id1 = KSUID.new().toString();
const id2 = KSUID.new().toString();
// Can be sorted alphabetically to reflect time
uuid (v4) is completely random. You cannot infer creation order from the ID itself.
// uuid: No sort order
const id1 = uuidv4();
const id2 = uuidv4();
// No correlation between string value and time
nanoid is random by default. You can make it sortable only by implementing custom alphabets or logic, which is not built-in.
// nanoid: Random by default
const id = nanoid();
// Not sortable without custom implementation
cuid included a timestamp component but collisions occurred when multiple processes generated IDs in the same millisecond.
// cuid: Time-based but collision-prone
const id = cuid();
// Contains time but unreliable for strict ordering
shortid did not guarantee sortability. It focused purely on brevity.
// shortid: Not sortable
const id = shortid();
// No time component embedded
nanoid offers the simplest API with zero dependencies. It is tree-shakable and very small.
// nanoid: Minimal setup
import { nanoid } from 'nanoid';
const id = nanoid();
uuid requires importing specific versions (v1, v4, etc.) to avoid bundling unused code. It is heavier than nanoid.
// uuid: Modular imports required
import { v4 } from 'uuid';
const id = v4();
ulid is lightweight and has a straightforward function export. It balances size and features well.
// ulid: Simple function export
import { ulid } from 'ulid';
const id = ulid();
ksuid often requires instantiating a class or object before generating, adding slight complexity.
// ksuid: Class-based usage
import KSUID from 'ksuid';
const id = KSUID.new().toString();
cuid was simple but is now legacy. The API is straightforward but the underlying logic is flawed.
// cuid: Simple but legacy
import cuid from 'cuid';
const id = cuid();
shortid had a very simple API, which contributed to its popularity before deprecation.
// shortid: Simple but deprecated
import shortid from 'shortid';
const id = shortid();
shortid is officially deprecated. The maintainer recommends switching to nanoid. Using it in new projects introduces security risks.
// shortid: DO NOT USE
// npm shows deprecation warning
// Migration path: switch to nanoid
cuid (original) is in maintenance mode with known issues. The community suggests @paralleldrive/cuid2 or nanoid.
// cuid: Legacy version
// Known collision issues in high concurrency
// Use cuid2 or nanoid instead
uuid, nanoid, ulid, and ksuid are actively maintained. They receive regular updates for security and compatibility.
// Active packages
import { v4 } from 'uuid';
import { nanoid } from 'nanoid';
import { ulid } from 'ulid';
// All safe for production use
While the differences are clear, these libraries share common goals and patterns. Here are key overlaps:
// All return strings
const id1 = nanoid();
const id2 = uuidv4();
const id3 = ulid();
// typeof id1 === "string"
// Works everywhere
// import works in Webpack, Vite, Node, etc.
import { nanoid } from 'nanoid';
// All designed for uniqueness
// Probability of collision is low for all active packages
| Feature | nanoid | uuid | ulid | ksuid | cuid | shortid |
|---|---|---|---|---|---|---|
| Status | ✅ Active | ✅ Active | ✅ Active | ✅ Active | ⚠️ Legacy | ❌ Deprecated |
| Secure | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ⚠️ Partial | ❌ No |
| Sortable | ❌ No | ❌ No | ✅ Yes | ✅ Yes | ⚠️ Partial | ❌ No |
| Length | Short (21 chars) | Long (36 chars) | Medium (26 chars) | Medium (27 chars) | Medium (25 chars) | Short (7-14 chars) |
| Dependencies | None | None | None | None | None | None |
nanoid is the default choice for modern frontend development — it is secure, small, and simple. Use it for client-side IDs, temporary tokens, or document keys where sortability does not matter.
ulid is the best option when you need time-based sorting without exposing raw timestamps. It fits well in distributed databases that benefit from chronological indexing.
uuid remains the standard for interoperability. If you are integrating with legacy systems or services that expect RFC4122 IDs, stick with uuid.
shortid and cuid should be avoided in new work. They have known flaws that nanoid and ulid solve better.
Final Thought: ID generation seems simple, but picking the wrong tool can cause database bottlenecks or security gaps. Match the library to your data requirements — security, sortability, or compatibility — and avoid deprecated tools.
Choose cuid only for legacy maintenance where IDs are already in use. The original package has known collision risks in high-throughput systems and is no longer recommended for new work. If you need fingerprint-based IDs, evaluate @paralleldrive/cuid2 instead.
Choose ksuid if you require k-sortable unique IDs similar to ULID but prefer the specific K-SUID specification often used in backend services. It is less common in the frontend ecosystem and may add unnecessary complexity compared to ULID.
Choose nanoid for most frontend use cases requiring secure, URL-friendly IDs with a tiny footprint. It is the recommended replacement for shortid and offers better performance and security with a simpler API.
Do NOT choose shortid for new projects. It is deprecated due to security vulnerabilities and collision issues. Migrate existing implementations to nanoid immediately.
Choose ulid when you need IDs that are sortable by creation time without exposing timestamps directly. It is ideal for database indexing where insertion order matters and human readability is a bonus.
Choose uuid when strict RFC4122 compliance is required for interoperability with existing systems or databases. It is the industry standard but produces longer strings and has a larger bundle size than alternatives.
Collision-resistant ids optimized for horizontal scaling and binary search lookup performance.
Note: All monotonically increasing (auto-increment, k-sortable), and timestamp-based ids share the security issues with Cuid. V4 UUIDs and GUIDs are also insecure because it's possible to predict future values of many random algorithms, and many of them are biased, leading to increased probability of collision. Likewise, UUID V6-V8 are also insecure because they leak information which could be used to exploit systems or violate user privacy. Here are some example exploits:
Currently available for Node, browsers, Java, Ruby, .Net, Go, and many other languages (see ports below — more ports are welcome).
cuid() returns a short random string with some collision-busting measures. Safe to use as HTML element ID's, and unique server-side record lookups.
ESM:
import cuid from 'cuid';
console.log( cuid() );
// cjld2cjxh0000qzrmn831i7rn
Node style:
var cuid = require('cuid');
console.log( cuid() );
// cjld2cyuq0000t3rmniod1foy
$ npm install --save cuid
** c - h72gsb32 - 0000 - udoc - l363eofy **
The groups, in order, are:
In browsers, the first chars are obtained from the user agent string (which is fairly unique), and the supported mimeTypes (which is also fairly unique, except for IE, which always returns 0). That string is concatenated with a count of variables in the global scope (which is also fairly unique), and the result is trimmed to 4 chars.
In node, the first two chars are extracted from the process.pid. The next two chars are extracted from the hostname.
Modern web applications have different requirements than applications from just a few years ago. Our modern unique identifiers have a stricter list of requirements that cannot all be satisfied by any existing version of the GUID/UUID specifications:
Today's applications don't run on any single machine.
Applications might need to support online / offline capability, which means we need a way for clients on different hosts to generate ids that won't collide with ids generated by other hosts -- even if they're not connected to the network.
Most pseudo-random algorithms use time in ms as a random seed. Random IDs lack sufficient entropy when running in separate processes (such as cloned virtual machines or client browsers) to guarantee against collisions. Application developers report v4 UUID collisions causing problems in their applications when the ID generation is distributed between lots of machines such that lots of IDs are generated in the same millisecond.
Each new client exponentially increases the chance of collision in the same way that each new character in a random string exponentially reduces the chance of collision. Successful apps scale at hundreds or thousands of new clients per day, so fighting the lack of entropy by adding random characters is a losing strategy.
Because of the nature of this problem, it's possible to build an app from the ground up and scale it to a million users before this problem rears its head. By the time you notice the problem (when your peak hour use requires dozens of ids to be created per ms), if your db doesn't have unique constraints on the id because you thought your guids were safe, you're in a world of hurt. Your users start to see data that doesn't belong to them because the db just returns the first ID match it finds.
Alternatively, you've played it safe and you only let your database create ids. Writes only happen on a master database, and load is spread out over read replicas. But with this kind of strain, you have to start scaling your database writes horizontally, too, and suddenly your application starts to crawl (if the db is smart enough to guarantee unique ids between write hosts), or you start getting id collisions between different db hosts, so your write hosts don't agree about which ids represent which data.
Because entities might need to be generated in high-performance loops, id generation should be fast. That means no waiting around for asynchronous entropy pool requests, or cross-process/cross-network communication. Performance slows to impracticality in the browser. All sources of entropy need to be fast enough for synchronous access.
Even worse, when the database is the only guarantee that ids are unique, that means that clients are forced to send incomplete records to the database, and wait for a network round-trip before they can use the ids in any algorithm. Forget about fast client performance. It simply isn't possible.
That situation has caused some clients to create ids that are only usable in a single client session (such as an in-memory counter). When the database returns the real id, the client has to do some juggling logic to swap out the id being used.
If client side ID generation were stronger, the chances of collision would be much smaller, and the client could send complete records to the db for insertion without waiting for a full round-trip request to finish before using the ID.
Cuids generated by the same process are monotonically increasing, when less than 10000 cuids are generated within the same millisecond. Generated by different processes, the cuids will still have an increasing value in time if the process clocks are synchronized.
Monotonically increasing IDs are suitable for use as high-performance database primary keys, because they can be binary searched. Pure pseudo-random variants don't meet this requirement.
Somewhat related to performance, an algorithm to generate an ID should require a tiny implementation. This is especially important for thick-client JavaScript applications.
Client-visible ids often need to have sufficient random data that it becomes practically impossible to try to guess valid IDs based on an existing, known id. That makes simple sequential ids unusable in the context of client-side generated database keys.
Most stronger forms of the UUID / GUID algorithms require access to OS services that are not available in browsers, meaning that they are impossible to implement as specified.
Because of the timestamp and the counter, cuid is really good at generating unique IDs on one machine.
Because of the fingerprints, cuid is also good at preventing collisions between multiple clients.
Because cuids can be safely generated synchronously, you can generate a lot of them quickly. Since it's unlikely that you'll get a collision, you don't have to wait for a round trip to the database just to insert a complete record in your database.
Because cuids are monotonically increasing, database primary key performance gets a significant boost.
Weighing in at less than 1k minified and compressed, the cuid source should be suitable for even the lightest-weight mobile clients, and will not have a significant impact on the download time of your app, particularly if you follow best practices and concatenate it with the rest of your code in order to avoid the latency hit of an extra file request.
Cuids contain enough random data and moving parts as to make guessing another id based on an existing id practically impossible. It also opens up a way to detect for abuse attempts -- if a client requests large blocks of ids that don't exist, there's a good chance that the client is malicious, or trying to get at data that doesn't belong to it.
The only part of a cuid that might be hard to replicate between different clients is the fingerprint. It's easy to override the fingerprint method in order to port to different clients. Cuid already works standalone in browsers, or as a node module, so you can use cuid where you need to use it.
The algorithm is also easy to reproduce in other languages. You are encouraged to port it to whatever language you see fit.
Need a smaller ID? cuid.slug() is for you. With 7 to 10 characters, .slug() is a great solution for short urls. Slugs may grow up to 10 characters as the internal counter increases. They're good for things like URL slug disambiguation (i.e., example.com/some-post-title-<slug>) but absolutely not recommended for database unique IDs. Stick to the full cuid for database keys.
Be aware, slugs:
are less likely to be monotonically increasing. Stick to full cuids for database lookups, if possible.
have less random data, less room for the counter, and less room for the fingerprint, which means that all of them are more likely to collide or be guessed, especially as CPU speeds increase.
Don't use them if guessing an existing ID would expose confidential information to malicious users. For example, if you're providing a service like Google Drive or DropBox, which hosts user's private files, favor cuid() over .slug().
No. Cuid is great for the use case it was designed for -- to generate ids for applications which need to be scalable past tens or hundreds of new entities per second across multiple id-generating hosts. In other words, if you're building a web or mobile app and want the assurance that your choice of id standards isn't going to slow you down, cuid is for you.
However, if you need to obscure the order of id generation, or if it's potentially problematic to know the precise time that an id was generated, you'll want to go with something different.
Cuids should not be considered cryptographically secure (but neither should most guid algorithms. Make sure yours is using a crypto library before you rely on it).
A sha1 implementation in JavaScript is about 300 lines by itself, uncompressed, and its use would provide little benefit. For contrast, the cuid source code weighs in at less than 100 lines of code, uncompressed. It also comes at considerable performance cost. Md5 has similar issues.
Almost all web-technology identifiers allow numbers and letters (though some require you to begin with a letter -- hence the 'c' at the beginning of a cuid). However, dashes are not allowed in some identifier names. Removing dashes between groups allows the ids to be more portable. Also, identifier groupings should not be relied on in your application. Removing them should discourage application developers from trying to extract data from a cuid.
The cuid specification should not be considered an API contract. Code that relies on the groupings as laid out here should be considered brittle and not be used in production.
Created by Eric Elliott.