Guide

Compared with other IDs

TNIDs carry a name like a prefixed ID and fit a UUID column like a UUID. Use them unless one of the exceptions applies.

FormatName in the bits?Fits a UUID column?Time-sortable?String lengthCase-sensitive string?
UUIDv4NoYesNo36No
UUIDv7NoYesTo the millisecond36No
ULIDNoAs raw bits, without UUID version bitsTo the millisecond26No
KSUIDNoNo (160 bits)To the second27Yes
TypeIDNo, only in the stringWithout the prefixUsually, when it wraps a UUIDv7Prefix + 27 (31 for user)Lowercase only
Prefixed UUID, such as user_ + UUIDNo, only in the stringWithout the prefixIf the UUID is time-orderedPrefix + 37 (41 for user)Typically no
TNIDYes, up to 4 charactersYes (it is a UUIDv8)TNIDv0, to the millisecond, per name19 to 22Yes
  • Name in the bits: a TypeID or prefixed UUID string stored in a uuid column reads back as a plain UUID, with no record of what it identifies. A TNID keeps its name in every form.
  • Fits a UUID column: native UUID type, 16-byte storage and indexes; passes through tools that expect UUIDs.
  • Case-sensitive string: needs a binary collation when stored as text: Text columns.

When to use another ID format

These are uncommon. Pick something else if you need:

  • Names over 4 characters, or characters other than a–z and 0–4. TypeID prefixes can have up to 63 characters.
  • Over about 100 trillion random TNIDv1s under one name. The chance of any collision is then about 1 in 250 for TNIDv1, and about 1 in a billion for UUIDv4.
  • Very high volume per millisecond under one name, or creation order within a millisecond. Same-millisecond TNIDv0s are unordered; a million same-name TNIDs in one millisecond have about a 1 in 290,000 chance of a collision. Some UUIDv7 and ULID generators keep order with a counter.
  • To pass validators you can't change that reject version 8, such as Yup's string().uuid() or Hibernate's @UUID default. Databases and standard parsers accept any version.