DEV Community

Pirate Prentice
Pirate Prentice

Posted on

n8n XML Node: Parse, Convert, and Build XML in Your Workflows [Free Workflow JSON]

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

  1. Add an XML node after a node that returns XML data (HTTP Request, Read Binary File, etc.).
  2. Set Mode to XML.
  3. Set Property Name to the field that contains the XML string (e.g., data, body, or xml).

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" } }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

This becomes:

{ "price": { "$": { "currency": "USD" }, "_": "19.99" } }
Enter fullscreen mode Exit fullscreen mode

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

  1. Set Mode to JSON.
  2. Set Property Name to the field containing the JavaScript object you want to convert.
  3. 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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Becomes:

<request>
  <customerId>12345</customerId>
  <action>lookup</action>
</request>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Summary

  • XML → JSON: Use for parsing SOAP responses, XML feeds, sitemaps. Set explicitArray: false almost always.
  • JSON → XML: Use for building SOAP request bodies or XML output payloads.
  • Most pain comes from namespace prefixes and the explicitArray default — 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)