UXID (UXID v2.5.0)

View Source

Generates 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

Many of the concepts of Stripe IDs have been used in this library.

Summary

Types

Options for generating a UXID

t()

A UXID represented as a String

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 ~September 2039.

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.

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

option()

@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}

Options for generating a UXID

options()

@type options() :: [option()]

t()

@type t() :: String.t()

A UXID represented as a String

Functions

autogenerate(opts)

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: true

cast(data, params)

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

compact_small_times()

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 ~September 2039.

decode(uxid)

@spec decode(String.t()) :: {:ok, UXID.Codec.t()}

Decodes a UXID string and returns a Codec struct with extracted components.

default_delimiter()

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.

dump(data, dumper, params)

Dumps the given term into an Ecto native type.

embed_as(format, params)

Dictates how the type should be treated if embedded. For UXIDs, we use :self since they're already strings.

encode_case()

equal?(left, right, params)

Checks if two UXIDs are equal.

generate(opts \\ [])

@spec generate(opts :: options()) :: {:ok, t()}

Returns an encoded UXID string along with response status.

generate!(opts \\ [])

@spec generate!(opts :: options()) :: t()

Returns an unwrapped encoded UXID string.

init(opts)

Converts the options specified in the field macro into parameters to be used in other callbacks.

load(data, loader, params)

Loads the given term into a UXID.

min_size()

Returns the minimum size configuration. When set, any requested size smaller than this will be upgraded.

monotonic()

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.

new(opts \\ [])

@spec new(opts :: options()) :: {:ok, UXID.Codec.t()}

Returns a new UXID.Codec struct. This is useful for development.

type(opts)

Returns the underlying schema type for a UXID.

valid?(term, opts \\ [])

@spec valid?(
  term(),
  keyword()
) :: boolean()

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, see default_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