Skip to content

Choosing a strategy

Use this page when you know what the value is for and need the strategy that fits. Start at the flowchart, check the table for the trade-off, and read the entropy section before shortening anything.

yes

no

yes

no

yes

Guid

string

yes

no

no

yes

no

What is the value for?

Does it grant access

or protect data?

Bytes(32)

see Secrets and tokens

Do people read

or type it?

NanoId(size, alphabet)

with a uniqueness check

Should it sort

by creation time?

Guid column

or string?

UuidV7()

Should it name

its type?

TypeId(prefix)

or cus_ + lowercase Ulid()

Ulid()

Compact string

needed?

NanoId()

Uuid()

When the value depends on the operation’s time, use the context overloads. See Context-dependent values.

Random bits are the unpredictable part of each value. Collision figures are in the next section.

SituationCodeExample outputRandom bitsTrade-off
Ordinary entity idGenerateValue.Uuid()212f34b8-1ff0-4da8-8c22-a43dece95de8122The default; reveals nothing, does not sort
Index-friendly Guid columnGenerateValue.UuidV7()01a13571-f1e8-7c3c-8d30-16b90f487b0274Appends to B-tree indexes; reveals creation time
Sortable string idGenerateValue.Ulid()01M4TQ3WF8QS2APSEB06E8VGDK80Sorts by age; reveals creation time; not monotonic within a millisecond
Self-describing id in logsGenerateValue.TypeId("user")user_01m4tq3wf8fs7s4ahqpprc3gt474The prefix names the type; time-ordered
Stripe-style public id$"cus_{GenerateValue.Ulid().ToLowerInvariant()}"cus_01m4tq3wcw1fn5912rd8rsmgcd80Familiar format; prefix and case are plain string work
Short typed codeGenerateValue.NanoId(10, "23456789ABCDEFGHJKLMNPQRSTUVWXYZ")SA65PT7D2G50No look-alike characters; needs a uniqueness check
Compact URL-safe idGenerateValue.NanoId()qlBnph5vVBJeX4gbqCwm512621 characters
Token, reset link, API keyGenerateValue.Bytes(32)gziGp2TEJPHWl6ztCXqiKMj7NVcyO3JhGD7t59Qe204256Secret; see Secrets and tokens
Hex tokenGenerateValue.Bytes(32, ValueEncoding.Hex)45421c194ffabca6…ad8d1d256Lowercase hex, 64 characters
TOTP secret, key materialGenerateValue.Bytes(20, ValueEncoding.Base32)PQ5URI2LDHCF6LRMMPYRUPMPRRS5UPGH160Case-insensitive, easy to type
Time follows the operationGenerateValue.TypeId("order", context)order_01j0dy9j4nej4saxp79mmcqts774One captured instant per operation
Tenant in the value$"{context.Tenant}-{GenerateValue.Ulid(context)}"00000000-…-000000000000-01J0DY9J4N…80Rarely needed; namespaces already isolate tenants

A TypeID’s 74 random bits are the UUID v7 bits. The ULID and cus_ rows differ in encoding from TypeID: a TypeID suffix encodes a UUID v7, a ULID does not.

Time-ordered ids are unique in practice only because the random part is large enough. The number of values at which the chance of any collision reaches 1% is about the square root of 2 × 2^bits × ln(1/0.99):

Random bitsValues at 1% collision risk
32about 9 thousand
40about 150 thousand
50about 4.8 million
64about 600 million
74about 20 billion
80about 160 billion
122about 3 × 10¹⁷
  • Below 64 bits, generate only with a uniqueness check, such as a unique index and a retry.
  • Size for the total population over the life of the system, not for one request.
  • No strategy guarantees uniqueness.
  • The client supplied the id. A client-supplied identifier is an input. Keep it; do not replace it with a generated one.
  • The same input must give the same id. Random generation does not deliver idempotency: a retry creates a different id. Fundamentals has no deterministic, name-based id strategy today. Persist and reuse the first id, or derive the id yourself.
  • The number must be gapless or legal. Invoice and tax numbering needs an allocator that records every number. Random ids have gaps by design.
  • The value would authorize something. A generated id identifies; it never proves who is calling. Never use an id as a credential.
  • A secret would come from a time-ordered id. UUID v7, ULID and TypeID expose creation time and have fewer random bits. Use Bytes.

Not available today: date-segment patterns such as INV-2026-…, random integers and check digits. Compose those from your own code around GenerateValue.