How to Convert JSON to XML in n8n With Attributes, Repeated Elements and Lookups

Table of contents
- Why converting JSON to XML is more than a format switch
- How target paths describe XML
- Step by step: build the invoice
- 1. Install the node
- 2. Start from the order JSON
- 3. Add the mappings
- 4. Choose the output settings
- 5. Check the result
- 6. Send it as a file instead
- Pitfalls I would check before going live
- Wrapping up
- FAQ
- Related resources
To convert JSON to XML in n8n without a Code node, add the Data Mapper node, set Output Format to XML, and write your target paths using three conventions: $ for attributes, _ for element text, and [*] for repeated elements. Each mapping is one row in a table, so the XML structure is visible in the target column instead of buried in string-building code.
This guide builds a real XML invoice from an order, with an attribute, a country lookup, a rounded amount, a reformatted date, a generated timestamp and a repeated line item. I ran every mapping below through the node, and the XML shown is its actual output. If you have not met the node yet, Data Mapper: An n8n Node That Reshapes JSON and XML Without a Code Node covers what it does.
Why converting JSON to XML is more than a format switch
A partner’s XML schema almost never looks like your JSON. The names differ, the nesting differs, some values must be attributes instead of elements, the date format is different, and country codes are spelled out. Converting the syntax is the easy part. Reshaping the data to the schema is the work.
Building the XML by string concatenation in a Code node is the usual shortcut, and it breaks in familiar ways: a forgotten & in a customer name produces invalid XML, and a list with one item is handled differently from a list with five. The data has to be mapped, transformed and escaped, in that order.
How target paths describe XML
Data Mapper builds the XML from the target path of each mapping, using the same conventions as n8n’s built-in XML node:
| Target path | XML it produces |
|---|---|
invoice.customer.name |
<invoice><customer><name>…</name></customer></invoice> |
invoice.$.id |
an attribute: <invoice id="…"> |
invoice.amount._ |
the text of an element that also has attributes: <amount>…</amount> |
invoice.lines.line[*].$.sku |
one <line sku="…"> per list element |
flowchart LR
accTitle: From mapping rows to an XML invoice
accDescr: A JSON order is read by Data Mapper. Mapping rows with target paths are applied: paths with a dollar sign become attributes, paths ending in an underscore become element text, and paths with a star in brackets become repeated elements. Transforms trim, look up, round and format values on the way. The result is written as XML text or an XML file.
J["JSON order<br/>OrderID · Items[*] · Amount"]
R["Mapping rows<br/>source → target path + transform"]
A["$ → attribute"]
T["_ → element text"]
L["[*] → repeated element"]
X["XML invoice<br/>text or file"]
J --> R
R --> A
R --> T
R --> L
A --> X
T --> X
L --> X
Step by step: build the invoice
1. Install the node
In n8n, open Settings → Community Nodes, choose Install, and enter n8n-nodes-data-mapper. Search for Data Mapper in the node panel.
2. Start from the order JSON
This is the input item:
{
"OrderID": "A1",
"CustomerName": " Ann de Vries ",
"Country": "NL",
"Currency": "eur",
"Amount": "129.456",
"OrderDate": "2026-10-17",
"Items": [
{ "SKU": "P-1", "Qty": "2" },
{ "SKU": "P-2", "Qty": "1" }
]
}
Notice the mess a real feed has: padded whitespace, a lowercase currency, a number stored as text, and quantities as strings.
3. Add the mappings
Set Define Mappings to Using Fields Below and add these rows:
| Source | Target | Transform |
|---|---|---|
OrderID |
invoice.$.id |
|
Fixed Value n8n |
invoice.$.source |
|
CustomerName |
invoice.customer.name |
Trim |
Country |
invoice.customer.country |
Lookup Table: NL=Netherlands and DE=Germany |
Amount |
invoice.amount._ |
Round, 2 decimal places |
Currency |
invoice.amount.$.currency |
Uppercase |
OrderDate |
invoice.issuedDate |
Format Date, output dd/MM/yyyy |
Fixed Value {{ $now.toISO() }} |
invoice.generatedAt |
|
Items[*].SKU |
invoice.lines.line[*].$.sku |
|
Items[*].Qty |
invoice.lines.line[*].qty |
To Integer |
Two rows target invoice.amount: one writes the text with _, the other writes the currency attribute with $. That is how an element with both an attribute and text is expressed. The two line[*] rows share a target array, so they build the same <line> elements instead of making two sets.
4. Choose the output settings
- Output Format: XML. The text lands in a field named
xmlby default. - Unmapped Fields: Drop, so only mapped fields appear in the invoice.
- Include XML Declaration: on, to add
<?xml version="1.0" encoding="UTF-8"?>. - Root Element: leave empty. Every mapping starts with
invoice, so that single top-level field becomes the root.
5. Check the result
This is the output from the real node. The timestamp is fixed here so the output is reproducible, and in your workflow it is the time of the run:
<?xml version="1.0" encoding="UTF-8"?>
<invoice id="A1" source="n8n">
<customer>
<name>Ann de Vries</name>
<country>Netherlands</country>
</customer>
<amount currency="EUR">129.46</amount>
<issuedDate>17/10/2026</issuedDate>
<generatedAt>2026-10-17T09:00:00.000Z</generatedAt>
<lines>
<line sku="P-1">
<qty>2</qty>
</line>
<line sku="P-2">
<qty>1</qty>
</line>
</lines>
</invoice>
The padding is trimmed, NL became Netherlands, 129.456 rounded to 129.46, the currency is uppercase, the ISO date is reformatted, and both items became <line> elements.
6. Send it as a file instead
For an SFTP drop or an email attachment, set Output Format to XML File. Set Root Element to invoice if your mappings do not already start with it, and set File Name to an expression like {{ $json.number }}.xml. Then connect an FTP, S3 or email node. The file lands in a binary field named data by default.
Pitfalls I would check before going live
- Special characters are escaped for you.
Ann & Co <x>came out asAnn & Co <x>. You do not need a manual replace the way you do with a hand-written template. - Trailing zeros disappear.
10.50rounded to two places came out as10.5. If the partner’s schema demands two decimals, handle that formatting in a step after the mapping. - Several top-level fields get a wrapper. With two top-level targets and no Root Element, the node wraps them in
<root>. Set the Root Element to the name your schema expects. - A single list item is still a list. One entry in
Itemsstill becomes one<line>element, so you do not need separate handling for one item and many. - A missing source is skipped by default. The element is left out. Choose Set to Null to get an empty element, or Stop With Error when the schema requires the field. The error names the mapping, for example
Mapping 1 (→ o.must): Source "zzz" was not found in the input. - Dates are UTC. Input dates in ISO 8601 are read as they are, and the tokens are
yyyy MM dd HH mm ss SSS. A time zone conversion needs its own step.
Wrapping up
The fastest way to avoid a broken XML feed is to treat the target path as the schema. Once the mapping table reads like the XML you need, review is a matter of reading rows, not tracing a script. If the target is XML text that you must build by hand, such as a SOAP request, the Message Template node is the better fit, and Message Template: Render JSON, XML, CSV and HL7 in n8n Without a Code Node covers it. To go the other way and understand when XML is worth the trouble, see JSON vs XML: Which Is Better for Data Feed APIs.
FAQ
How do I write an XML attribute in n8n?
With Data Mapper, put $ in the target path before the attribute name, for example invoice.$.id for <invoice id="…">. Element text goes under _ when the element also has attributes or children.
Can n8n's built-in XML node do this?
The XML node converts JSON to XML using the structure it is given, so the shape has to be right before it runs. Data Mapper does the reshaping (renaming, lookups, formatting) and the conversion in one step, which is useful when the target schema looks different from your source.
How do I create repeated XML elements from a JSON array?
Use [*] in both the source and the target path, for example Items[*].SKU to invoice.lines.line[*].$.sku. Each array element becomes one repeated element, and several mappings sharing the same target array fill in the same elements.
How do I format a date for an XML feed?
Use the Format Date transform with an output format such as dd/MM/yyyy. Leave the input format empty to read ISO 8601 or Unix milliseconds, or give it a pattern like yyyyMMdd.
Can I output a file instead of text?
Yes. Choose XML File as the output format and set the file name, which can be an expression. The file goes into a binary field, ready for FTP, S3 or an email attachment.

