Back

Generating Unique IDs with the Web Crypto API

Generating Unique IDs with the Web Crypto API

Every frontend developer eventually faces this problem: you need a unique identifier for client-side data, but there’s no server-generated ID yet. Maybe you’re building a todo list, managing form fields, or tracking items before they’re persisted. You need something reliable, and you need it now.

The good news? Modern browsers ship with a built-in solution. The Web Crypto API provides crypto.randomUUID()—a simple, secure way to generate unique IDs in the browser without installing any libraries.

Key Takeaways

  • crypto.randomUUID() produces RFC-compliant, cryptographically secure v4 UUIDs natively in the browser with zero dependencies.
  • Always generate IDs at object creation time, not during render cycles.
  • The method requires a secure context (HTTPS or localhost).
  • For environments lacking crypto.randomUUID(), use crypto.getRandomValues() as a lightweight fallback.

Why crypto.randomUUID() Should Be Your Default

The crypto.randomUUID() method produces RFC 9562 (formerly RFC 4122) version-4 UUIDs using a cryptographically secure random number generator. The output looks like this:

550e8400-e29b-41d4-a716-446655440000

That’s a 36-character string with 122 bits of randomness. The collision probability is astronomically low—you’d need to generate a billion UUIDs per second for about 85 years to reach a 50% chance of a single collision.

Here’s how simple it is:

function generateId() {
  return crypto.randomUUID()
}

const newItem = { id: generateId(), label: 'My item' }

This method works in all modern browsers (Chrome 92+, Firefox 95+, Safari 15.4+), runs in Web Workers, and requires no dependencies.

The Secure Context Requirement

One critical detail: crypto.randomUUID() only works in secure contexts. This means HTTPS in production or localhost during development. If you’re testing over plain HTTP on a non-localhost domain, the method won’t be available.

This requirement exists because the Web Crypto API provides cryptographic primitives that shouldn’t be exposed in insecure environments.

Why Older Patterns Fall Short

Before crypto.randomUUID() became widely available, developers often reached for alternatives that seem reasonable but have real problems.

Math.random() isn’t cryptographically secure. Its output can be predictable, and collisions are far more likely than you’d expect in production applications.

Timestamps (like Date.now()) fail when multiple IDs are generated in the same millisecond—a common scenario in loops or batch operations.

Ad-hoc generators combining timestamps with random numbers are better, but they’re reinventing a solved problem with less rigor than the platform provides natively.

For secure client-side ID generation, crypto.randomUUID() is the clear choice.

Practical Frontend Considerations

Generate Once, Store Immediately

The most common mistake is generating IDs during render cycles. Instead, create the ID when the object is created and store it:

// ✓ Correct: ID generated at creation time
function addItem(label) {
  return { id: crypto.randomUUID(), label }
}

// ✗ Wrong: ID generated during render
items.map(item => <li key={crypto.randomUUID()}>{item.label}</li>)

Generating a new UUID on every render defeats the purpose of stable keys. React, for example, uses keys to track elements across re-renders. A new key each time forces unnecessary DOM teardown and recreation.

SSR and Browser Environments

If you’re using server-side rendering, crypto.randomUUID() is available in Node.js 19+ and recent versions of other runtimes. For older Node versions or edge cases, check for availability before calling:

const id = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
  ? crypto.randomUUID()
  : fallbackGenerator()

A Lightweight Fallback Strategy

For environments where crypto.randomUUID() isn’t available, crypto.getRandomValues() has broader support (including Node.js 16+) and can generate UUIDs without libraries:

function fallbackUUID() {
  const bytes = crypto.getRandomValues(new Uint8Array(16))
  bytes[6] = (bytes[6] & 0x0f) | 0x40 // Version 4
  bytes[8] = (bytes[8] & 0x3f) | 0x80 // Variant 10
  
  const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('')
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
}

This manually sets the version and variant bits to produce a valid v4 UUID, using the same cryptographically secure random source under the hood.

When Libraries Still Make Sense

While crypto.randomUUID() covers most cases, you might reach for a library like uuid or nanoid when you need:

  • Non-v4 UUID versions (v1, v5, v7)
  • Shorter IDs for URLs or user-facing displays
  • Guaranteed cross-environment compatibility with older runtimes

But for standard secure client-side ID generation, the native API should be your starting point.

Conclusion

crypto.randomUUID() gives you RFC-compliant, cryptographically secure UUIDs with zero dependencies. Generate IDs at object creation time, not during renders. Use HTTPS in production. For edge cases, fall back to crypto.getRandomValues(). The browser already has what you need—use it.

FAQs

Yes, v4 UUIDs generated by crypto.randomUUID() are suitable as primary keys. The 122 bits of cryptographic randomness make collisions practically impossible. However, be aware that random UUIDs can cause index fragmentation in some databases. If insert performance matters at scale, consider UUID v7, which is time-ordered, though that requires a library.

It requires a secure context because the Web Crypto API exposes cryptographic primitives. Browsers restrict these to HTTPS and localhost to prevent man-in-the-middle attacks from tampering with cryptographic operations. During local development on localhost, it works without HTTPS. In production, you need a valid TLS certificate.

Yes. The crypto object and its randomUUID method are available in Web Workers, Service Workers, and Shared Workers, as long as the context is secure. This makes it convenient for generating IDs in background threads without needing to communicate back to the main thread for ID creation.

crypto.randomUUID() produces standard 36-character v4 UUIDs using the browser's built-in cryptographic random generator. nanoid generates shorter, URL-friendly IDs with a customizable alphabet and length, and works across more environments out of the box. Choose randomUUID for standards compliance and zero dependencies. Choose nanoid when you need compact IDs or broader runtime support.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay