Whole schemas, as one would write them, each followed by data it takes or refuses (# good: Name, # bad: Name). The forms are explained in the tutorial, the rules in the reference.

A configuration file

Strict about what it takes, generous with defaults:

%{
  Config: %schema{
    service: {:refine :string %{pattern: @regex "^[a-z][a-z0-9-]{1,30}$"}}
    listen: {:refine :string %{format: :host-port}}
    workers: %field{type: {:refine :int32 %{min: 1, max: 64}}, default: 4}
    timeout: %field{type: {:refine :duration %{max: @duration "PT5M"}}, default: @duration "PT30S"}
    log: %field{type: {:enum :debug :info :warn :error}, default: :info}
    database: %shape{
      url: {:refine :string %{format: :uri}}
      pool: %field{type: {:refine :int32 %{min: 1}}, default: 10}
    }
    features?: {:refine {:set-of :keyword} %{max-count: 32}}
  }
}
# good: Config
%Config{
  service: "orders"
  listen: "0.0.0.0:8080"
  database: %{url: "postgres://db.internal/orders"}
}
# bad: Config
%Config{service: "Orders", listen: "0.0.0.0:8080", database: %{url: "postgres://db.internal/orders"}}

A schema document with named types

%{
  Percentage: {:refine :float %{min: 0.0, max: 100.0}}
  Tag:        {:refine :string %{min-length: 1, max-length: 20}}

  Address: %schema{
    street: :string
    city:   :string
    zip?:   :string
  }

  Customer: %schema{
    name:    :string
    address: Address
    loyalty: Percentage
    tags?:   {:list-of Tag}
  }
}

Percentage and Tag are named types, used like a struct's name.

# good: Customer
%Customer{name: "Ada", address: %Address{street: "1 Main St", city: "London"}, loyalty: 42.5}

An API response, paged

A generic page wraps any kind of item:

%{
  Page: {:generic [t] %shape{
    items: {:list-of t}
    total: {:refine :int64 %{min: 0}}
    next?: {:refine :string %{format: :uri}}
  }}

  User: %shape{
    id: :uuid
    name: {:refine :string %{min-length: 1, max-length: 100}}
    email: {:refine :string %{format: :email}}
  }

  UserPage: {:of Page User}
}
# good: UserPage
%{
  items: [%{id: @uuid "6f9c1a8e-3b2d-4e5f-9a7b-1c2d3e4f5a6b", name: "Ada", email: "ada@example.com"}]
  total: 1
}

Events on a bus

Each event's payload is chosen by its type:

%{
  Event: %shape{
    type: {:enum "order.placed" "order.cancelled" "customer.registered"}
    at: {:refine :datetime %{offsets: ["Z"]}}
    payload: {:case type %{
      "order.placed" => OrderPlaced
      "order.cancelled" => %shape{order: :uuid, reason: CancelReason}
      :else => :any
    }}
  }

  OrderPlaced: %shape{
    order: :uuid
    lines: {:list-items Line {:repeat Line 0 nil}}
  }

  Line: %shape{sku: :string, qty: {:refine :int32 %{min: 1}}}

  # A known reason, or a short text: exactly one of them.
  CancelReason: {:one-of {:enum :customer_request :out_of_stock}
                         {:refine :string %{max-length: 200}}}
}
# good: Event
%{
  type: "order.placed"
  at: @datetime "2026-10-21T09:30:00Z"
  payload: %{order: @uuid "6f9c1a8e-3b2d-4e5f-9a7b-1c2d3e4f5a6b", lines: [%{sku: "TEE-00042", qty: 1}]}
}
# bad: Event
%{
  type: "order.placed"
  at: @datetime "2026-10-21T09:30:00Z"
  payload: %{order: @uuid "6f9c1a8e-3b2d-4e5f-9a7b-1c2d3e4f5a6b", lines: []}
}

The second has no line: an order needs at least one.

Money, exactly

%{
  Money: {:doc %shape{
    amount: {:refine :decimal %{min-exponent: -2, max-exponent: 0, max-precision: 18}}
    currency: {:refine :keyword %{pattern: @regex "^[a-z]{3}$"}}
  } %{description: "An amount in a currency, to the cent", examples: [%{amount: 19.99M, currency: :eur}]}}
}
# bad: Money
%{amount: 19.995M, currency: :eur}

Three decimal places are one too many.

A tree, and a value like JSON

%{
  Category: %shape{
    name: {:refine :string %{min-length: 1}}
    children: %field{type: {:list-of Category}, default: []}
  }

  Json: {:any-of :nil :boolean :number :string {:list-of Json} {:map-of :string Json}}

  Settings: %shape{name: :string, extra: {:map-of :string Json}}
}
# good: Settings
%{name: "theme", extra: %{"colors" => ["#000" "#fff"], "contrast" => 4.5, "dark" => true}}

An expression language

Three types that refer to each other, each case choosing its arguments:

%{
  Expr: %shape{
    op: {:enum :lit :add :neg :if}
    args: {:case op %{
      :lit => {:list-items :number}
      :add => {:list-items Expr Expr {:repeat Expr 0 nil}}
      :neg => {:list-items Expr}
      :if  => {:list-items Cond Expr Expr}
    }}
  }

  Cond: %shape{
    op: {:enum :and :not :lt}
    args: {:case op %{
      :and => {:list-items Cond {:repeat Cond 1 nil}}
      :not => {:list-items Cond}
      :lt  => {:list-items Expr Expr}
    }}
  }
}
# good: Expr
%{op: :if, args: [
  %{op: :lt, args: [%{op: :lit, args: [1]} %{op: :lit, args: [2]}]}
  %{op: :lit, args: [10]}
  %{op: :add, args: [%{op: :lit, args: [1]} %{op: :lit, args: [2]} %{op: :lit, args: [3]}]}
]}
# bad: Expr
%{op: :add, args: [%{op: :lit, args: [1]}]}

:add takes two arguments or more.

A schema, version by version

Each .dxns document describes one version of a schema, in its header. user-v1.dxns:

%{
  User: %schema{
    name: :string
    email: :string
  }
}

user-v2.dxns, a field appended:

%{
  User: %schema(2){
    name: :string
    email: :string
    id?: :uuid
  }
}

The first has no header, so it is version 1. Load both and a reader keeps User v1 and v2 side by side; loading user-v2.dxns a second time is an error (schema/duplicate-version), because two documents claiming to be User v2 is a mistake rather than an update. A struct says which version it was written with:

# good: User
%User(2){name: "Ada", email: "ada@example.com"}

Schemas across files

acme/common.dxns holds a namespace's small types, each named acme.common/...:

%{
  Money: %shape{amount: :decimal, currency: :keyword}
  Code: {:refine :string %{pattern: @regex "^[A-Z]{3}$"}}
}

acme/shop/Order.dxns holds acme.shop/Order, and refers to them by their full names:

%{
  Order: %schema{
    total: acme.common/Money
    country: acme.common/Code
  }
}

A reader finds a name's file by the same rules (DXNS.md ยง6), and a tree's check -- mix dextrin.check acme/ -- reports a name that resolves to nothing.

Everything at once

bench/schemas/universe.dxns, the schema the schema-validation benchmark checks against, uses nearly every form there is in one document -- mutual recursion, nested generics, cases inside cases, a .dxnb document inside a base64 string -- and is more complex than any real schema is likely to be.