The functions a use UXID.Registry module defines and the options a defid key accepts. The examples use the MyApp.IDs module from Declaring a registry; the tasks these serve are in How to govern prefixes with a registry.

By key - minting and schema configuration

MyApp.IDs.generate!(:org)   # => "org_01h..."
MyApp.IDs.prefix(:org)      # => "org"
MyApp.IDs.size(:org)        # => :medium
MyApp.IDs.schema(:org)      # => MyApp.Org
MyApp.IDs.all()             # => [%{key: :org, prefix: "org", schema: MyApp.Org, ...}, ...]

field_opts/1 is the single-source-of-truth hook - a schema spreads it instead of restating prefix/size/validate anywhere:

@primary_key {:id, UXID, [autogenerate: true] ++ MyApp.IDs.field_opts(:org)}

generate!/2 merges caller options over the registry's, so a call site can pass anything the key does not own:

MyApp.IDs.generate!(:share, monotonic: false)   # one-off override
MyApp.IDs.generate!(:export, from: natural_key) # deterministic - see Deterministic keys

:prefix and :size belong to the key and raise if passed - the registry's whole contract is that a key determines its shape. Drop to UXID.generate!/1 if you genuinely need a one-off shape.

Body-shape options

:size is not the only thing that decides what a body looks like. Three more options can be declared on the key, for the same reason: they change the ID's shape, so every call site and every schema field has to agree on them.

defid :event,   prefix: "evt", size: :small, monotonic: true
defid :session, prefix: "ses", compact_time: true
defid :item,    prefix: "itm", rand_size: 4
OptionValuesEffect
:monotonictrue, false, a list of sizesOpts the key into (or out of) monotonic generation without consulting the global policy
:compact_timetrue, falseSpends 40 rather than 48 bits on the timestamp, moving the freed byte into the random field
:rand_sizea non-negative integerAn explicit random-byte count, overriding the width implied by :size

Leave one unset and the key defers to the global application configuration exactly as UXID.generate!/1 does, so declaring nothing changes nothing. Set it and it flows into both generate!/2 and field_opts/1 - so an Ecto autogenerate: true field mints the same shape as an explicit call, with the declaration living in one place:

@primary_key {:id, UXID, [autogenerate: true] ++ MyApp.IDs.field_opts(:event)}

A call site can still override any of the three for a one-off (generate!(:event, monotonic: false)); unlike :prefix and :size they are defaults, not pins.

Registry-wide defaults are available for the two policy-shaped ones, alongside :default_size and :default_validate:

use UXID.Registry,
  default_size: :medium,
  default_monotonic: [:small, :medium],
  default_compact_time: false

Malformed values are compile errors, like everything else the registry checks: an unknown size (in :size or in a :monotonic list) would otherwise fall through to :xlarge and silently mint the wrong shape. Declaring both deterministic: true and monotonic: true is rejected too - the pair can never mint, since one asks for a stable hash and the other for burst-random bits.

By ID string - the runtime routing table

This is the "which resource is this?" map that powers authorization scans, admin tooling, and global-ID resolution:

MyApp.IDs.known?("org_01h...")      # => true   (cheap prefix-only membership check)
MyApp.IDs.key_for("org_01h...")     # => :org
MyApp.IDs.schema_for("org_01h...")  # => MyApp.Org
MyApp.IDs.resolve("org_01h...")     # => %{key: :org, schema: MyApp.Org, category: :account, ...}

Lookups split an ID on the last delimiter, which is unambiguous without any registry lookup because a UXID body is Crockford Base32 and never contains the delimiter - so in_ref_01h... recovers the in_ref prefix cleanly. For that reason the :delimiter must be a character that cannot appear in a Base32 body ("_" - the default - or "-"); an underscore is preferred for compound prefixes since it does not break double-click-to-select-the-whole-id.