UXID

UXID

MIT License CI Hex Version Hex Downloads Hex Docs

User eXperience focused IDentifiers for Elixir: prefixed, K-sortable, Stripe-style IDs a person can read, copy and route back to their resource, with optional Ecto types and a prefix registry. An ID looks like usr_01epey2p06tr1rtv07xa82zgjj: the prefix names the resource, and the body carries a timestamp and randomness in lowercase Crockford Base32.

Why UXID

An auto-increment key leaks how many rows a table holds and invites enumeration; a random UUID fixes both but is long, unordered and anonymous, so in a log line, a URL or a support thread nobody can tell what it points at, and a double-click selects only part of it. A UXID names its resource in its prefix, selects whole on a double-click, reads aloud without ambiguity, sorts by creation time so it indexes well, and is generated in the application with no coordination between nodes. Its size is tunable for low-cardinality resources, a monotonic mode keeps a burst within one millisecond unique and ordered, a deterministic mode maps the same input to the same ID, and a registry keeps every prefix in an app unique and routes an ID back to its resource. Why a UXID has a prefix, a time and randomness explains the trade-offs behind these properties.

Install

Add uxid to your dependencies in mix.exs:

def deps do
  [
    {:uxid, "~> 2.9"}
  ]
end

Ecto is an optional dependency: UXID only uses it if your app already does.

Basic usage

# No options generates a plain ULID
UXID.generate!()                              # "01emdgjf0dqxqj8fm78xe97y3h"

# Add a prefix to name the resource
UXID.generate!(prefix: "cus")                 # "cus_01emdgjf0dqxqj8fm78xe97y3h"

# Shrink the random part for low-cardinality resources
# T-shirt sizes: :xs :s :m :l :xl (or :xsmall :small :medium :large :xlarge)
UXID.generate!(prefix: "cus", size: :small)   # "cus_01eqrh884aqyy1"

# Deterministic: same input -> same id, forever (prefix is the namespace)
UXID.generate!(prefix: "usr", from: "alice@example.com")
# => "usr_zcvt7epac0t1ebcsjfyf7cwz25"

# As an Ecto field type, primary keys included, with the same options
defmodule YourApp.User do
  use Ecto.Schema

  @primary_key {:id, UXID, autogenerate: true, prefix: "usr", size: :medium}
  schema "users" do
    field :api_key, UXID, autogenerate: true, prefix: "apikey", size: :small
  end
end

Documentation

Compatibility

mix.exs declares elixir: "~> 1.8". CI runs the full quality gate on the toolchain mise.toml pins and the test suite alone on Elixir 1.16 / OTP 25, the oldest pair it is tested on. There is no required runtime dependency. Ecto is optional ({:ecto, "~> 3.12"}): when it is loaded, UXID is also an Ecto.ParameterizedType; without it, everything but the Ecto type works the same.

License

UXID is released under the MIT License.