Data Mapper: An n8n Node That Reshapes JSON and XML Without a Code Node

Table of contents
- What changed since 0.2.0
- How the visual mapper works
- What you can drop into a target field
- An order mapped to XML and CSV
- Map Automatically, and where it stops
- Source Profile and Source Data
- Classic mode for existing workflows
- Why a mapping node beats a Code node for this job
- Security hardening in 0.4.0
- Install it
- Limits to know about
- FAQ
- Related resources
Data Mapper is an open-source n8n community node that maps whatever comes in onto a target structure and outputs XML, JSON or CSV. You paste a target profile, drag fields from n8n’s Input panel onto the target fields (or let Map Automatically match them by name), and run it. I built it because most “reshape this payload” work in n8n ends up as a Code node nobody else on the team wants to read.
It is on npm as n8n-nodes-data-mapper, MIT licensed, with the source on GitHub. The current version is 0.4.1. This post first covered 0.2.0, and I rewrote it for the 0.4 releases because the node changed a lot: a visual mapper is now the default, CSV is a new output, and the old path-based mode lives on as classic mode.
What changed since 0.2.0
- Visual mapper (node version 2, the default for new nodes). Paste a target profile and its fields load as a tree. Drag fields from the Input panel onto them.
- CSV output, on top of XML and JSON.
- Source Profile and Source Data. Paste the structure of the incoming data to get a dropdown of source fields, or map raw XML or JSON text sitting in a field or file.
- Smarter auto-map (0.4.1). Abbreviations such as
Qtyforquantitynow match, and you can add your own synonyms. - Security hardening, covered near the end.
- Classic mode is unchanged. Existing workflows keep node version 1, so nothing changes for them.
flowchart LR
accTitle: How the visual mapper works
accDescr: A target profile is pasted into the Data Mapper node and loads as a tree of target fields. Fields from n8n's Input panel are dragged onto those target fields, or Map Automatically matches them by name. The output follows the order of the target profile and is written as XML, JSON or CSV.
PROF["Target profile<br/>XML · JSON · schema · XSD · CSV header"]
TREE["Target fields<br/>order › line[] › sku"]
IN["n8n Input panel<br/>or Map Automatically"]
OUT["Output<br/>XML · JSON · CSV"]
PROF --> TREE
IN --> TREE
TREE --> OUT
How the visual mapper works
- Output Format: choose XML, JSON or CSV.
- Target Profile: paste the structure you must produce. It can be an XML, JSON or flat file profile component, JSON Schema, sample JSON or XML, an XSD, or a CSV header. Its fields appear as a tree, where
›separates levels,[]marks a repeating element,@an XML attribute,(number)a type and*a required field. - Map each field: drag a field from the Input panel into the target box, or set the mode to Map Automatically.
- Run it. The output always follows the order of the target profile, which matters to XML consumers and XSD validation.
You do not need a source profile. Anything the previous node outputs can be dragged from the Input panel.
What you can drop into a target field
| You put in a target box | What happens |
|---|---|
A dragged field, {{ $json.OrderID }} |
That field is copied across. |
A dragged field from a list, {{ $json.Items[0].SKU }}, on a repeating target |
Every element is mapped, one target element per item, not just the first. |
A single field on a repeating target, {{ $json.Currency }} |
The value is written into every element. |
Any expression, {{ $json.Currency.toUpperCase() }} |
Its result is used. Expressions work as the transform step. |
An expression returning a list, {{ $json.Items.map(i => i.SKU) }} |
Element n of the list goes to element n of the target. |
| Fixed text | Written as it is. |
| Nothing | The field is left out. Turn on Keep Empty Values to write it empty. |
Numbers and booleans are converted where the profile says so (Convert Types From Profile), and a value that cannot be converted is kept as it is instead of failing the run.
An order mapped to XML and CSV
Take this incoming order:
{
"OrderID": "A1",
"Currency": "eur",
"Items": [
{ "SKU": "P-1", "Qty": "2" },
{ "SKU": "P-2", "Qty": "1" }
]
}
For XML, the target profile is this sample, and I mapped four fields:
<order id="A1"><currency>EUR</currency><line><sku>P-1</sku><quantity>2</quantity></line></order>
| Target field | Dropped value |
|---|---|
@id |
{{ $json.OrderID }} |
currency |
{{ $json.Currency.toUpperCase() }} |
line[] › sku |
{{ $json.Items[0].SKU }} |
line[] › quantity |
{{ $json.Items[0].Qty }} |
I ran that through the 0.4.1 node, and this is its output:
<?xml version="1.0" encoding="UTF-8"?>
<order id="A1">
<currency>EUR</currency>
<line>
<sku>P-1</sku>
<quantity>2</quantity>
</line>
<line>
<sku>P-2</sku>
<quantity>1</quantity>
</line>
</order>
The two line elements came from dropping Items[0] onto a repeating target, and the root name order came from the sample profile, so no Root Element was needed.
For CSV, use a sample JSON profile with a repeating lines list, and map the same four fields. By default every incoming item becomes rows in one CSV with a header row, and each element of a repeating target gets its own row, with the other columns repeated:
orderId,currency,lines.sku,lines.quantity
A1,EUR,P-1,2
A1,EUR,P-2,1
CSV options cover the delimiter, the header row and the line ending, and you can choose one CSV for the whole run or one per item.
Map Automatically, and where it stops
Map Automatically matches source fields to target fields by similar names. On the order above, with a sample JSON profile of orderId, currency and a lines list holding sku and quantity, it matched all four fields on 0.4.1:
OrderIDtoorderIdCurrencytocurrencyItems[*].SKUtolines[*].skuItems[*].Qtytolines[*].quantity, converted to the number 2 because the profile saysquantityis a number
That last match is new in 0.4.1. On 0.4.0, Qty was left unmatched at the default confidence. Now names are compared token by token with built-in abbreviations and synonyms, so Amt maps to amount, OrderNo to orderNumber, fname to firstName, zip to postalCode, and British and American spellings line up.
It is still a name matcher, not a mind reader. A German export with a Menge column stays unmatched against quantity, until you add menge=quantity to Custom Synonyms (one source=target per line, or a JSON object). I tested both runs: without the synonym the report listed lines[*].quantity as unmatched, and with it the value mapped.
Turn on Add Auto-Map Report and the output gets an _autoMap field listing what matched and what did not, so you can map the leftovers by hand. You can also lower Auto-Map Minimum Confidence (default 0.6) to let it guess more. Auto-map does not transform values either: eur stayed eur, because a name match is not a rule to uppercase it.
Source Profile and Source Data
Two settings help when the incoming data is not already a tidy item:
- Source Data lets you map the incoming item, or XML and JSON text in a field or file, for example a raw order in
data. - Source Profile is an optional paste of the incoming structure, such as a sample order. Every target field then gets a dropdown of source fields, like picking the source side of a line in a visual mapper. You can still switch any field to an expression.
A source profile made from a one-item sample still maps every item of a real order: when a picked field sits inside a list in the actual data, each element becomes its own target element.
Classic mode for existing workflows
Nodes created before 0.3.0 use classic mode, which maps by typed paths with built-in transforms. It still works, and it is the mode to read if you maintain older workflows. Each mapping is one row: source path, target path, optional transform.
| Source Path | Target Path | Transform |
|---|---|---|
id |
orderId |
|
customer.name |
buyer.name |
Trim |
customer.country |
buyer.country |
Lookup Table: NL=Netherlands |
items[*].sku |
lines[*].productCode |
|
items[*].qty |
lines[*].quantity |
To Integer |
Fixed Value EUR |
lines[*].currency |
The path rules for arrays are explicit, which is where hand-written mapping code usually goes wrong:
| Source and target | What happens |
|---|---|
Same number of [*] |
Elements are matched by position. Several mappings can fill one array of objects. |
Fewer [*] in the target |
The extra levels are collected into a list: items[*].sku → skus gives ["P-1", "P-2"]. |
More [*] in the target |
Not allowed, except for a fixed value, which is written to every existing element. |
One object where [*] expects an array |
Treated as a one-element array. |
Classic mode also has transforms (string, number, integer, boolean, round, trim, case, date format, split, join, lookup table), a Using JSON mode that stores the whole mapping as data you can load from a database row, and an Auto-Map From Profile mode. Date formatting uses the tokens yyyy MM dd HH mm ss SSS in UTC.
Why a mapping node beats a Code node for this job
A mapping is data, and a Code node is a program. When someone asks “where does quantity come from?”, a field with a visible value answers in one glance. A script makes you read it top to bottom and trust that the loop at line 40 does what its name says.
It also fails more usefully. In classic mode every error names the mapping that caused it, for example Mapping 3 (→ lines[*].quantity): Cannot convert "two" to a number. With Settings → On Error → Continue, a failed item comes out as { "error": "…" } instead of stopping the run. I still use a Code node when the logic is genuinely conditional. For anything that is “take this shape and make it that shape”, the mapper is shorter and easier to review.
Security hardening in 0.4.0
Mapping nodes read field names and XML from outside your system, so this release tightened that boundary:
- No prototype pollution. Field names and paths such as
__proto__orconstructorare treated as plain data, whether they come from mappings, profiles, lookup tables or the data. - Own fields only. Source paths read the data’s own fields, never inherited members.
- Safe XML parsing. DTDs and entity declarations are ignored, so there are no external entities. Nesting deeper than 1,000 levels is rejected with a clear error.
- No code execution. Mappings, profiles and lookup tables are data, and the node never evaluates them. n8n evaluates expressions, as in any node.
- CSV injection. Turn on Escape CSV Formulas and values starting with
=,+,-,@, a tab or a carriage return get a leading'. I checked that=HYPERLINK("http://x")comes out as'=HYPERLINK("http://x"). Negative numbers get the prefix too, so leave the option off for numeric exports you control.
The package has no runtime dependencies.
Install it
In n8n, open Settings → Community Nodes, choose Install, enter n8n-nodes-data-mapper, and accept the risk notice. The node appears as Data Mapper. For self-hosted setups that copy nodes into the custom folder:
mkdir -p custom && cd custom
npm pack n8n-nodes-data-mapper
tar -xzf n8n-nodes-data-mapper-*.tgz && mv package n8n-nodes-data-mapper
Restart n8n afterwards. It targets n8n 1.x and 2.x. One quirk: if you import an example workflow and the node shows as ?, the node type prefix does not match your install. Community-node installs use n8n-nodes-data-mapper.dataMapper, while a custom-folder install registers it as CUSTOM.dataMapper. Add the node from the panel, or swap the prefix in the file.
Limits to know about
- The target field list comes from a pasted profile. If the profile is loaded by an expression, n8n cannot list its fields at design time, so paste it or use Map Automatically.
- Auto-map is a suggestion engine, not a guarantee. Names that look alike can still mean different things, and domain terms it does not know, like a German
Menge, need a custom synonym. - Transforms in the visual mapper are n8n expressions. The built-in transform list belongs to classic mode.
- It is still a 0.x release. The repo’s test suite (79 tests) passes on 0.4.1, but I would run your real payloads through it before putting it in a production flow.
FAQ
Is the Data Mapper node free to use?
Yes. It is open source under the MIT license, published on npm as n8n-nodes-data-mapper, with the code on GitHub. It has no runtime dependencies.
Will updating to the 0.4 releases break my existing workflows?
No. Existing Data Mapper nodes keep node version 1, which is classic mode, unchanged. Only new nodes get the visual mapper by default.
Can Data Mapper convert JSON to XML, and XML to JSON or CSV?
Yes. It reads JSON or XML from the incoming item, a text field or a file, and writes XML, JSON or CSV. For XML, attributes go under $ and element text under _, the same convention as n8n’s own XML node.
Do I need a source profile?
No. You can drag fields straight from n8n’s Input panel. A source profile is optional, and it adds a dropdown of source fields to each target field, which helps when the data arrives as raw XML or JSON text in a field.
Can it output CSV?
Yes, since 0.3.0. By default every incoming item becomes rows in one CSV with a header row, a repeating target gives one row per element, and you can set the delimiter, header and line ending.
Do I need to write JavaScript to use it?
No. You drag fields or pick them from dropdowns. JavaScript only comes in if you choose an n8n expression such as .toUpperCase() as a transform.
