If you're integrating with SOAP APIs, legacy enterprise systems, or any feed that still speaks XML, the n8n XML node is your bridge between the XML world and n8n's native JSON data model. It handles both directions: XML â JSON for parsing, and JSON â XML for building payloads.
This guide covers every option, the gotchas that catch people, and three copy-paste workflow patterns.
What the XML Node Does
The XML node has one job: convert data between XML and JSON formats. It exposes two modes:
| Mode | Input | Output | When to use |
|---|---|---|---|
| XML to JSON | XML string in a field | Parsed JSON object | Parsing SOAP responses, RSS/Atom feeds, sitemap files |
| JSON to XML | JSON object | XML string | Building SOAP request bodies, generating XML output |
Mode 1: XML to JSON
Basic setup
- Add an XML node after a node that returns XML data (HTTP Request, Read Binary File, etc.).
- Set Mode to
XML. - Set Property Name to the field that contains the XML string (e.g.,
data,body, orxml).
The node parses the XML and replaces the field with a structured JSON object.
The explicitArray option (most important)
By default, n8n's XML parser (xml2js) wraps every element in an array, even when there's only one child. This means <name>Alice</name> becomes { name: ["Alice"] } instead of { name: "Alice" }.
Fix: Enable the Explicit Array option. When set to false, single elements are returned as plain values; only actual multi-element sequences become arrays.
// explicitArray: true (default)
{ person: { name: ["Alice"], age: ["30"] } }
// explicitArray: false
{ person: { name: "Alice", age: "30" } }
Almost always set this to false unless you specifically need the array-always behavior.
Other useful options
| Option | What it does |
|---|---|
| Trim | Strip leading/trailing whitespace from terxt nodes |
| Ignore Attributes | Drop XML attributes entirely â useful when you only need element text |
| Attribute Key | Rename the $ key that holds XML attributes (default is $) |
| Char Key | Rename the _ key that holds text content alongside attributes |
| Explicit Root | When false, strips the root element wrapper from the output |
| Ignore Namespace | Strip XML namespace prefixes (e.g., soap:Body â Body) |
Handling XML attributes
When an element has both attributes and text content:
<price currency="USD">19.99</price>
This becomes:
{ "price": { "$": { "currency": "USD" }, "_": "19.99" } }
Access the currency with {{ $json.price['$'].currency }} and the value with {{ $json.price['_'] }}.
If you don't need attributes, enable Ignore Attributes to flatten to { "price": "19.99" }.
XML namespaces
Namespaced XML like <soap:Body> is common in SOAP responses. By default, the namespace prefix is kept as part of the key name. Use Ignore Namespace to strip prefixes so soap:Body becomes just Body â"much easier to reference in expressions.
Mode 2: JSON to XML
Basic setup
- Set Mode to
JSON. - Set Property Name to the field containing the JavaScript object you want to convert.
- The node converts the object to an XML string and stores it back in that field.
Controlling the output
// Input JSON
{
"request": {
"customerId": "12345",
"action": "lookup"
}
}
Becomes:
<request>
<customerId>12345</customerId>
<action>lookup</action>
</request>
Adding a declaration and root wrapper
For SOAP and many enterprise APIs you need the <?xml version="1.0"?> declaration. The XML node doesn't add this automatically â prepend it with a Set node or Code node after conversion:
// Code node after XML node
$input.item.json.body = `<?xml version="1.0" encoding="UTF-8"?>\n${$input.item.json.body}`;
return $input.item;
Adding XML attributes
To produce <price currency="USD">19.99</price> from JSON, structure your input with the $ attribute key:
{
"price": {
"$": { "currency": "USD" },
"_": "19.99"
}
}
3 Workflow Patterns
Pattern 1: SOAP API Integration
SOAP APIs send and receive XML. Here's the full round-trip:
HTTP Request (POST SOAP envelope)
â XML node (XML â JSON, ignore namespace, explicitArray false)
â Set node (extract the fields you need)
â ...rest of workflow
For the request, build your SOAP envelope in a Set node as a JSON string, then use the XML node (JSON â XML) to convert it, then pass it to HTTP Request with Content-Type: text/xml.
Free workflow JSON â SOAP lookup + response parse:
{
"nodes": [
{
"name": "Build SOAP Body",
"type": "n8n-nodes-base.set",
"parameters": {
"mode": "raw",
"jsonOutput": "={{ JSON.stringify({ Envelope: { Body: { GetCustomer: { customerId: $json.id } } } }) }}"
}
},
{
"name": "Convert to XML",
"type": "n8n-nodes-base.xml",
"parameters": { "mode": "json", "dataPropertyName": "jsonOutput" }
},
{
"name": "SOAP Request",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "POST",
"url": "https://your-soap-service.com/api",
"sendHeaders": true,
"headerParameters": {
"parameters": [{ "name": "Content-Type", "value": "text/xml; charset=utf-8" }]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "text/xml",
"body": "={{ $json.jsonOutput }}"
}
},
{
"name": "Parse Response",
"type": "n8n-nodes-base.xml",
"parameters": {
"mode": "xml",
"dataPropertyName": "data",
"options": { "explicitArray": false, "ignoreAttrs": true, "ignoreNameSpace": true }
}
}
]
}
Pattern 2: XML Sitemap Parser
Parse a sitemap.xml to extract all URLs for crawling, SEO auditing, or content processing:
HTTP Request (GET sitemap.xml)
â XML node (XML â JSON, explicitArray false)
â Split Out (split urlset.url array into items)
â Set node (extract loc, lastmod, priority)
The sitemap structure maps to:
$json.urlset.url[n].loc â page URL
$json.urlset.url[n].lastmod â last modified date
$json.urlset.url[n].changefreq â change frequency
$json.urlset.url[n].priority â SEO priority (0.0â1.0)
Pattern 3: XML Feed Transformer
Convert a vendor's XML product feed to JSON for import into a database or CRM:
Schedule Trigger (daily)
â HTTP Request (GET XML feed URL)
â XML node (XML â JSON, explicitArray false, trim true)
â Item Lists node (Split Out on products.product)
â Set node (normalize field names)
â Postgres / Airtable / Google Sheets (upsert)
Gotchas Table
| Gotcha | Symptom | Fix |
|---|---|---|
explicitArray: true (default) |
name["Alice"] instead of name: "Alice"
|
Set Explicit Array to false |
| Namespace prefixes in keys |
soap:Body hard to reference |
Enable Ignore Namespace |
Attributes in $ key |
price['$'].currency surprises newcomers |
Enable Ignore Attributes if attrs not needed |
| Root element wrapper | Output nested one level too deep | Enable Explicit Root: false |
| Missing XML declaration for SOAP | API returns 400 or "invalid envelope" | Prepend <?xml version="1.0"?> in Code/Set node |
Text content in _ key |
price._ instead of price
|
Use Char Key option to rename, or just reference _
|
| Large XML files | Memory spike on very large feeds | Split into chunks or use streaming HTTP + batched processing |
Useful Expressions After Parsing
// Get all items from a list (after explicitArray: false if single, array if multiple)
$json.feed.items.item // returns array or single object
// Safe access â handle both single item and array
const items = [].concat($json.feed.items.item); // always array
// Extract first match
$json.root.records.record[0].id
// Access attribute (currency) and text (_) on <price currency="USD">19.99</price>
$json.price['$'].currency // "USD"
$json.price['_'] // "19.99"
Summary
-
XML â JSON: Use for parsing SOAP responses, XML feeds, sitemaps. Set
explicitArray: falsealmost always. - JSON â XML: Use for building SOAP request bodies or XML output payloads.
- Most pain comes from namespace prefixes and the
explicitArraydefault â fix both upfront. - For SOAP, build a dedicated sub-workflow: Set (build envelope) â XML â HTTP Request â XML (parse).
What are you using the XML node for â SOAP integrations, feed parsing, or something else? Drop your use case below.
Top comments (0)