← Back to all articles

UUIDs and Random IDs: Can They Be Primary Keys

IdentifiersBeginner

Common versions

  • v1: time + MAC, sortable but may leak machine info;
  • v4: pure random, most common, collision chance negligible;
  • v7: time-prefixed, insertion-sortable, recommended for new projects.

Primary-key trade-offs

ProsCons
Generated independently, distributedLonger than auto-increment, bigger index
Hides total count / orderUnordered writes fragment B+tree indexes

Two reminders

  • Not a secret: UUIDs are enumerable; don't use them as reset tokens or permission proof;
  • Need order? Use v7: v4 is unordered; keep a timestamp if you sort by time.

Try it

Generate IDs: UUID generator.

Real-world cases: three identifier design mistakes

  1. Auto-increment IDs exposed publicly: /order/1024 leaks business volume and can be enumerated. Use UUID / ULID externally while keeping the auto-increment key internally.
  2. Using NanoID as a password: NanoID is a random identifier, not a secret; its default alphabet and length are not meant to carry secrecy. Tokens need sufficient entropy and an expiry.
  3. Ignoring case and ordering: storing lowercase but querying with mixed case returns nothing; and when time-ordering matters, choose ULID rather than v4.

FAQ

UUID v4 or ULID? Pick ULID when you want roughly time-ordered, shorter, readable IDs; pick v4 for pure randomness with no ordering need. Does a UUID leak information? v4 does not; v1 encodes a MAC address and timestamp, which can leak machine details. Can they be database primary keys? Yes, but fully random v4 causes random index writes — write-heavy workloads are better served by ordered ULID. Is collision truly negligible? At 122 random bits it is, but still keep a unique index as a safety net in distributed systems.

Generating and storing identifiers

  1. Randomness source: use cryptographically secure randomness (crypto.randomUUID() or getRandomValues in the browser, crypto.randomUUID() in Node) — never Math.random;
  2. Storage type: PostgreSQL's uuid type (16 bytes) halves the space of varchar(36); MySQL can use BINARY(16);
  3. Indexing and writes: random primary keys cause page splits and random I/O; write-heavy tables are better served by time-prefixed ordered IDs such as ULID, or by keeping an auto-increment key and using the UUID as a business key;
  4. Case and format: normalise to lowercase without hyphens on write, or equal IDs compare unequal;
  5. Format validation: regex-validate external IDs before querying so junk never reaches the index.

Public exposure and privacy

Public identifiers should be unguessable, so they do not reveal volume (/order/1024 tells the world how many orders exist) or allow enumeration. Internal logs and debugging, by contrast, benefit from sortable IDs — hence the common pattern of an internal auto-increment key plus a public UUID/ULID, linked by a mapping.

Common misconceptions

  • Treating ULID as "encrypted auto-increment": it only encodes time and holds no secret, so authorisation is still required;
  • Using short IDs as a security boundary: short codes can be enumerated — pair them with rate limiting and permission checks;
  • Relying on "it will not collide" without a unique index: the constraint is a mandatory safety net in distributed systems;
  • Mixing several ID shapes without documentation: different forms of the same resource across endpoints trip callers up — standardise and document.

IDs in URLs and logs

  • Short and URL-safe in URLs: NanoID or hyphen-free hex beats a braced UUID for readability;
  • Searchable in logs: log the full ID so it can be looked up exactly — truncation breaks correlation;
  • An ID is not a permission: knowing it does not grant access; authorise independently;
  • Avoid enumerable IDs on sensitive paths: sequences can be walked, so pair them with access control and rate limiting if you must use them.

Readability and error correction

IDs that people copy by hand or read aloud should exclude confusable characters (0/O, 1/l/I) and be displayed in groups; NanoID's custom alphabet handles this well. If manual entry is unavoidable, add a check digit so most typos are caught before submission.

Generating in bulk

Two cautions when producing many IDs at once: do not share one timestamp across ULIDs (within the same millisecond the random part should increase, or ordering degrades), and de-duplicate afterwards — with concurrent generators, a unique index is the last line of defence.