XML to Elm
Paste XML and instantly get idiomatic Elm type alias definitions with inferred field types.
Example
Input XML:
<book id="1" inStock="true">
<title>Programming Elm</title>
<pages>320</pages>
<tags>
<tag>functional</tag>
<tag>frontend</tag>
</tags>
</book>
Generated Elm:
module Generated exposing (..)
type alias Tags =
{ tag : List String
}
type alias Root =
{ id : Int
, inStock : Bool
, title : String
, pages : Int
, tags : Tags
}How it works
It parses the XML with the browser's DOMParser, builds a value tree from attributes, child elements and text, then infers a type model and emits Elm `type alias` records (repeated tags become List, missing fields become Maybe). The root type is named Root.
Good to know
XML to Elm turns a sample XML document into ready-to-use Elm type alias records, so Elm developers don't have to hand-write the shape of data they receive from a legacy SOAP service, an RSS/Atom feed, a config file, or any other XML API. You paste a representative document, click Convert, and get a module Generated block with one record per nested element, the root element always named Root.
It's most useful at the start of an integration, when you know what the XML looks like but haven't modeled it in Elm yet. The tool inspects actual values to pick types: it reads attributes (which appear as fields alongside child-element fields), promotes a child that repeats under the same parent to a List, and marks a field Maybe when it's present on some sibling elements but missing on others. Numeric text becomes Int or Float, true/false becomes Bool, and anything else falls back to String.
Read the output top-down: child types are emitted first so the Root alias at the bottom can reference them by name. A field typed List String means that tag appeared multiple times; a Maybe Int means it was optional and numeric in your sample. Keep a few things in mind when interpreting results:
- Inference is only as good as your sample — feed it XML that includes optional fields and repeated elements, or those types will be guessed wrong.
- The generated code gives you record types only, not JSON/XML decoders, so you'll still wire up parsing yourself.
- Type-name collisions on differently-nested elements with the same tag are auto-suffixed (e.g.
Node2), and Elm reserved words in field names get a trailing underscore.
A practical tip: if a value that should be a string keeps inferring as Int or Bool (for example a ZIP code or a literal "true" label), the tool is matching the value pattern, not your intent — you'll need to widen those fields to String by hand after generating. Everything runs locally via the browser's DOMParser, so nothing you paste is uploaded.
Frequently asked questions
How are XML attributes handled?
Each attribute becomes a record field on the element's type, with its type inferred from the value (for example inStock="true" becomes inStock : Bool).
How does it decide between List and Maybe?
If a child tag appears more than once under the same parent it becomes a List; if a field is present on some sibling elements but missing on others it becomes a Maybe.
Is my data uploaded anywhere?
No — this tool runs entirely in your browser. Your input never leaves your device and it works offline once loaded.
Is it free?
Yes, completely free with no sign-up and no limits.
People also ask
Does XML to Elm generate decoders or just type aliases?
It generates only Elm type alias records describing the data shape. You still need to write the decoding logic to turn XML or JSON into those records yourself.
Why is the root type always called Root?
The tool names the top-level element Root regardless of its actual tag name, then names nested record types after their element tags. You can rename Root in your editor after copying the output.
What happens if I paste invalid or malformed XML?
The browser's DOMParser flags a parser error and the tool shows a 'Could not parse XML' message instead of output. Fix unbalanced tags or stray characters and convert again.
How does it handle deeply nested XML elements?
Each nested element with children or attributes becomes its own type alias, and the parent record references it by type name. The types are listed child-first so every reference is defined before it is used.
Can it convert a single sample if some fields are optional?
Yes, but only across siblings within that one document. If a field appears on some repeated elements and not others it becomes Maybe; a field that is simply absent from your only sample cannot be detected as optional.
Does it support XML namespaces?
Namespace declarations like xmlns and xmlns: attributes are ignored and not turned into fields. Prefixed tag and attribute names are otherwise treated as ordinary identifiers and sanitized into valid Elm names.
How are numbers and booleans detected?
Whole numbers become Int, decimals and scientific notation become Float, and the literals true/false (any case) become Bool. When a field mixes Int and Float values across samples it widens to Float, and unrecognized text stays String.
What is text content turned into when an element also has attributes?
Mixed elements that have both attributes and inner text get a dedicated text field on the record, alongside the attribute fields. Pure text elements without attributes or children are inlined as a scalar type instead.
Related tools