UXID (UXID v2.7.0)
View SourceGenerates UXIDs and acts as an Ecto ParameterizedType
User eXperience focused IDentifiers (UXIDs) are identifiers which:
- Describe the resource (aid in debugging and investigation)
- Work well with copy and paste (double clicking selects the entire ID)
- Can be shortened for low cardinality resources
- Are secure against enumeration attacks
- Can be generated by application code (not tied to the datastore)
- Are K-sortable (lexicographically sortable by time - works well with datastore indexing)
- Do not require any coordination (human or automated) at startup, or generation
- Are very unlikely to collide (more likely with less randomness)
- Are easily and accurately transmitted to another human using a telephone
- Can optionally be deterministic (name-based) — the same input always maps to the same ID
Many of the concepts of Stripe IDs have been used in this library.
Deterministic IDs
Passing from: to generate/1, generate!/1, or new/1 produces a
deterministic (name-based) ID: the same input string always maps to the same
ID (UUIDv5-style), with the prefix acting as the namespace. Deterministic IDs
carry a leading z/Z marker and sort after time-based IDs. They are a hash
of a known input, so they are not unguessable — see generate/1 and the
Deterministic IDs guide for the full picture.
Summary
Functions
Generates a loaded version of the UXID.
Casts the given input to the UXID ParameterizedType with the given parameters.
Returns whether compact time encoding is enabled for small sizes.
When true, :xs/:xsmall and :s/:small use 40 bits for timestamp instead of 48,
adding 8 bits to randomness. This is a global policy that can be overridden
per-call with the compact_time option.
This provides perfect 5-bit Crockford Base32 alignment (8 chars vs 10 chars).
K-sortability is maintained until ~mid-2038 for compact mode: value 31 (the
first char z/Z) is reserved as the deterministic-ID scheme marker, so
compact encoding raises for timestamps at/after that band. Standard 48-bit
timestamps are unaffected.
Decodes a UXID string and returns a Codec struct with extracted components.
Returns the delimiter used to separate a prefix from the encoded body.
Defaults to "_" and can be overridden globally with the :delimiter
application env, or per-call/per-field with the :delimiter option.
Returns true if the given value is a deterministic (name-based) UXID.
Dumps the given term into an Ecto native type.
Dictates how the type should be treated if embedded. For UXIDs, we use :self since they're already strings.
Checks if two UXIDs are equal.
Returns an encoded UXID string along with response status.
Returns an unwrapped encoded UXID string.
Converts the options specified in the field macro into parameters to be used in other callbacks.
Loads the given term into a UXID.
Returns the minimum size configuration. When set, any requested size smaller than this will be upgraded.
Returns the global monotonic generation policy.
Returns a new UXID.Codec struct. This is useful for development.
Returns the underlying schema type for a UXID.
Returns true if the given value is a structurally valid UXID.
Types
@type option() :: {:case, atom()} | {:time, integer()} | {:size, atom() | nil} | {:rand_size, integer() | nil} | {:prefix, String.t() | nil} | {:delimiter, String.t() | nil} | {:compact_time, boolean() | nil} | {:monotonic, boolean() | [atom()] | nil} | {:from, String.t() | nil}
Options for generating a UXID
@type options() :: [option()]
@type t() :: String.t()
A UXID represented as a String
Functions
Generates a loaded version of the UXID.
Field options are threaded through to generate!/1, including monotonic
for opt-in monotonic generation:
field :id, UXID, autogenerate: true, prefix: "evt", size: :small, monotonic: trueDeterministic (from:) IDs are intentionally not wired here — there is no
per-row input available at autogenerate time. Mint them explicitly in
application code (e.g. in a changeset via generate!/1) and store/cast the
result as an ordinary string.
Casts the given input to the UXID ParameterizedType with the given parameters.
By default any binary is accepted unchanged (backwards compatible). When the
field opts in with validate: true, the value must be either a structurally
valid UXID carrying the field's configured :prefix (see valid?/2) or a
legacy bare UUID string; anything else casts to :error. UUID coexistence
can be turned off with allow_uuid: false.
field :owner_org_id, UXID, prefix: "org", validate: true
Returns whether compact time encoding is enabled for small sizes.
When true, :xs/:xsmall and :s/:small use 40 bits for timestamp instead of 48,
adding 8 bits to randomness. This is a global policy that can be overridden
per-call with the compact_time option.
This provides perfect 5-bit Crockford Base32 alignment (8 chars vs 10 chars).
K-sortability is maintained until ~mid-2038 for compact mode: value 31 (the
first char z/Z) is reserved as the deterministic-ID scheme marker, so
compact encoding raises for timestamps at/after that band. Standard 48-bit
timestamps are unaffected.
@spec decode(String.t()) :: {:ok, UXID.Codec.t()}
Decodes a UXID string and returns a Codec struct with extracted components.
Returns the delimiter used to separate a prefix from the encoded body.
Defaults to "_" and can be overridden globally with the :delimiter
application env, or per-call/per-field with the :delimiter option.
Returns true if the given value is a deterministic (name-based) UXID.
Deterministic IDs carry a leading z/Z scheme marker (Crockford value 31)
instead of an encoded timestamp, so this only inspects the first body
character — it does not otherwise validate structure (pair it with valid?/2
if you need both). Time-based IDs start with a lower value and return false.
Options
:delimiter- the prefix delimiter (defaults to the configured delimiter, seedefault_delimiter/0).
Examples
iex> UXID.deterministic?("usr_z9m4k7p2q3r8t5v6w0abcde")
true
iex> UXID.deterministic?("cus_01emdgjf0dqxqj8fm78xe97y3h")
false
iex> UXID.deterministic?("nope!")
false
Dumps the given term into an Ecto native type.
Dictates how the type should be treated if embedded. For UXIDs, we use :self since they're already strings.
Checks if two UXIDs are equal.
Returns an encoded UXID string along with response status.
Deterministic (name-based) IDs
Passing from: switches to deterministic mode: the same input string always
maps to the same ID (UUIDv5-style), across processes and machines, forever. The
prefix is folded in as the namespace, so the same string under two prefixes
yields two different bodies. from: must be a string (raises otherwise).
Deterministic IDs are marked with a leading z/Z and sort after every
time-based ID; they are not K-sortable among themselves. They are a hash of a
known input, so they are exactly as guessable as that input — do not derive
an ID from a low-entropy secret and treat the ID as unguessable.
{:ok, id} = UXID.generate(prefix: "usr", from: "alice@example.com")
# {:ok, "usr_z…"} — identical for this input on every call
Returns an unwrapped encoded UXID string.
Passing from: returns a deterministic (name-based) ID: the same input always
maps to the same ID, with the prefix acting as the namespace. See generate/1
for the full deterministic-mode notes.
UXID.generate!(prefix: "usr", from: "alice@example.com")
# => "usr_z…" (stable for this input, forever)
Converts the options specified in the field macro into parameters to be used in other callbacks.
Loads the given term into a UXID.
Returns the minimum size configuration. When set, any requested size smaller than this will be upgraded.
Returns the global monotonic generation policy.
When active, the random field is treated as a big-endian counter: the first ID in a millisecond is seeded from the CSPRNG and each subsequent same-ms ID advances it by a random positive step (per process, per prefix). This guarantees uniqueness and K-sortability within a burst while keeping consecutive IDs within a bounded window ahead — a mitigation, not cryptographic unpredictability — which is why it is off by default and opt-in per resource.
Accepts true/false, or a list of sizes (alias-aware, e.g. [:small]
matches both :small and :s). Overridable per-call/per-field with the
monotonic option. See UXID.Monotonic.
@spec new(opts :: options()) :: {:ok, UXID.Codec.t()}
Returns a new UXID.Codec struct. This is useful for development.
Returns the underlying schema type for a UXID.
Returns true if the given value is a structurally valid UXID.
A valid UXID is a binary made up of an optional prefix, the delimiter, and a
Crockford Base32 body of at least 8 characters. This checks
structure, not authenticity: it cannot distinguish a generated UXID from an
arbitrary Base32 string of the same shape, and it deliberately does not
accept a bare UUID (see cast/2's :validate mode for UUID coexistence).
Options
:prefix- when present, the value must carry exactly this prefix. When omitted, any prefix (or none) is accepted.:delimiter- the prefix delimiter (defaults to the configured delimiter, seedefault_delimiter/0).
Examples
iex> UXID.valid?("cus_01emdgjf0dqxqj8fm78xe97y3h")
true
iex> UXID.valid?("cus_01emdgjf0dqxqj8fm78xe97y3h", prefix: "cus")
true
iex> UXID.valid?("cus_01emdgjf0dqxqj8fm78xe97y3h", prefix: "usr")
false
iex> UXID.valid?("nope!")
false