Legacy APIs, enterprise systems, and data feeds still speak XML while your workflows speak JSON. That SOAP service returning inventory data? XML. The RSS feed you need to parse? XML. The configuration file from your vendor’s system? Also XML. Every time you try to work with this data in n8n, you hit a wall because the workflow expects JSON.
The XML node bridges this gap. It converts XML to JSON for processing in your workflows and transforms JSON back to XML when you need to send data to XML-based systems. No coding required. Whether you’re integrating with a decades-old enterprise system or parsing modern data feeds, this node handles the translation.
The XML-JSON Bridge Challenge
XML and JSON represent data differently:
- XML uses tags and attributes with strict hierarchy rules (XML specification)
- JSON uses objects and arrays with flexible nesting
- XML attributes have no direct JSON equivalent
- XML namespaces add complexity that JSON avoids
Manual conversion is tedious and error-prone. Copy-pasting between online converters breaks automation. The XML node handles these structural differences automatically, mapping elements to properties and preserving the relationships in your data.
What You’ll Learn
- When to use the XML node versus alternatives like Code node or Extract from File
- Both conversion modes: XML to JSON and JSON to XML
- All configuration options and what they actually do
- How attribute keys and character keys work
- Processing SOAP API responses in your workflows
- Parsing RSS feeds and XML data files
- Creating XML output for legacy system integration
- Troubleshooting common conversion errors
- Best practices for reliable XML processing
When to Use the XML Node
Before adding the node, confirm it’s the right tool. The XML node handles format conversion, not data extraction or complex transformations.
| Scenario | Use XML Node? | Why |
|---|---|---|
| Convert SOAP API response to JSON | Yes | Direct XML string to JSON conversion |
| Parse RSS/Atom feed data | Yes | Standard XML feeds convert cleanly |
| Process XML configuration files | Yes | First extract text, then convert |
| Create XML for legacy API request | Yes | JSON to XML with proper formatting |
| Transform XML from HTTP Request | Yes | Works directly with response data |
| Extract specific values from XML | Maybe | Convert first, then use Edit Fields |
| Complex XML with namespaces | Yes | Namespaces become JSON prefixes |
| Streaming large XML files | No | Node loads entire content to memory |
Rule of thumb: Use the XML node when you need complete XML-to-JSON or JSON-to-XML conversion. For partial extraction from converted data, add an Edit Fields node afterward.
When Not to Use the XML Node
The XML node has specific limitations:
| Limitation | What Happens | Better Approach |
|---|---|---|
| Binary XML files | Node expects string input | Use Extract from File first (see binary data guide) |
| XPath queries | No XPath support | Convert to JSON, then use expressions |
| XSLT transformations | Not supported | Use Code node with libraries |
| XML validation | No schema validation | Validate externally before processing |
| Selective element parsing | Converts entire document | Convert all, filter with Edit Fields |
| Very large XML files (>50MB) | Memory constraints | Process in chunks or use streaming tools |
Understanding the Two Conversion Modes
The XML node operates in two distinct modes. Each direction has different options because the data structures have different characteristics.
XML to JSON
Converts XML markup into JSON objects. The node parses the XML structure and maps elements, attributes, and text content to JSON properties.
Input example:
<?xml version="1.0" encoding="UTF-8"?>
<order id="12345">
<customer>John Doe</customer>
<items>
<item sku="ABC123">
<name>Widget</name>
<quantity>3</quantity>
<price>29.99</price>
</item>
</items>
<total currency="USD">89.97</total>
</order>
Output (with default settings):
{
"order": {
"$": {
"id": "12345"
},
"customer": ["John Doe"],
"items": [{
"item": [{
"$": { "sku": "ABC123" },
"name": ["Widget"],
"quantity": ["3"],
"price": ["29.99"]
}]
}],
"total": [{
"$": { "currency": "USD" },
"_": "89.97"
}]
}
}
Notice how:
- Attributes appear under the
$key - Text content with attributes appears under the
_key - Elements become arrays by default (Explicit Array option)
- The root element becomes a top-level property
Best for:
- Processing SOAP API responses
- Parsing RSS and Atom feeds
- Converting XML data exports
- Reading XML configuration files
JSON to XML
Converts JSON objects into valid XML documents. The node follows specific conventions to map JSON properties back to XML elements and attributes.
Input example:
{
"order": {
"$": {
"id": "12345"
},
"customer": "John Doe",
"item": {
"$": { "sku": "ABC123" },
"name": "Widget",
"quantity": 3
}
}
}
Output:
<?xml version="1.0" encoding="UTF-8"?>
<order id="12345">
<customer>John Doe</customer>
<item sku="ABC123">
<name>Widget</name>
<quantity>3</quantity>
</item>
</order>
Best for:
- Creating SOAP request bodies
- Generating XML for legacy APIs
- Building XML configuration files
- Formatting data for XML-based systems
Quick Comparison
| Aspect | XML to JSON | JSON to XML |
|---|---|---|
| Input format | XML string | JSON object |
| Output format | JSON object | XML string |
| Attribute handling | Maps to $ key | Reads from $ key |
| Text with attributes | Maps to _ key | Reads from _ key |
| Arrays | Elements become arrays | Arrays become repeated elements |
| Primary use | Parsing API responses | Creating API requests |
Your First XML Conversion
Let’s build a complete workflow that fetches XML data and converts it to JSON.
Step 1: Fetch XML Content
First, use the HTTP Request node to retrieve XML data:
- Add an HTTP Request node to your workflow
- Set Method to GET
- Enter a URL that returns XML (for testing, use a public RSS feed)
- Click Test step
The node returns XML content in the response body.
Step 2: Add the XML Node
- Add an XML node after HTTP Request
- Set Mode to “XML to JSON”
- For Property Name, enter the field containing your XML (typically
datafrom HTTP Request) - Leave other options at defaults for now
Step 3: Test the Conversion
Click Test step. The output shows your XML converted to a JSON structure:
{
"rss": {
"$": { "version": "2.0" },
"channel": [{
"title": ["Example Feed"],
"link": ["https://example.com"],
"item": [
{ "title": ["First Article"], "link": ["https://example.com/1"] },
{ "title": ["Second Article"], "link": ["https://example.com/2"] }
]
}]
}
}
Step 4: Access Converted Data
Use expressions in subsequent nodes to access the converted JSON:
// Access feed title
{{ $json.rss.channel[0].title[0] }}
// Access first item's title
{{ $json.rss.channel[0].item[0].title[0] }}
// Get all item titles
{{ $json.rss.channel[0].item.map(i => i.title[0]) }}
Notice the array notation [0] - this is because Explicit Array is enabled by default, wrapping elements in arrays.
Configuration Deep Dive
Understanding every option prevents unexpected conversion results.
Property Name
The field containing your data to convert.
For XML to JSON:
- Enter the name of the property containing the XML string
- Common values:
data,body,xml, or the field from your previous node - If you’re testing with sample XML, you can paste it directly into a Set node first
For JSON to XML:
- Enter the name of the property containing the JSON object
- Usually references data from previous workflow nodes
- The property value must be a valid JSON object, not a string
Attribute Key (Default: $)
Controls how XML attributes appear in JSON output (XML to JSON) or where the node looks for attributes (JSON to XML).
XML to JSON example:
With Attribute Key set to $, this XML:
<product id="123" active="true">Widget</product>
Becomes:
{
"product": {
"$": { "id": "123", "active": "true" },
"_": "Widget"
}
}
Accessing attributes in expressions:
{{ $json.product.$.id }} // "123"
{{ $json.product.$.active }} // "true"
JSON to XML example:
To create XML with attributes, structure your JSON with the $ key:
{
"product": {
"$": { "id": "123", "active": "true" },
"name": "Widget"
}
}
Output:
<product id="123" active="true">
<name>Widget</name>
</product>
Character Key (Default: _)
Controls how text content appears when an element has both attributes and text.
XML to JSON example:
With Character Key set to _:
<price currency="USD">29.99</price>
Becomes:
{
"price": {
"$": { "currency": "USD" },
"_": "29.99"
}
}
Accessing in expressions:
{{ $json.price._ }} // "29.99"
{{ $json.price.$.currency }} // "USD"
JSON to XML example:
To create an element with both attributes and text content:
{
"price": {
"$": { "currency": "USD" },
"_": "29.99"
}
}
Output:
<price currency="USD">29.99</price>
XML to JSON Options
These options only appear when converting XML to JSON:
| Option | Default | Effect |
|---|---|---|
| Explicit Array | On | Wraps all child elements in arrays |
| Explicit Root | On | Includes root element in output |
| Ignore Attributes | Off | Skips XML attributes entirely |
| Merge Attributes | Off | Merges attributes with child properties |
| Normalize | Off | Trims whitespace in text nodes |
| Normalize Tags | Off | Converts all tag names to lowercase |
Explicit Array explained:
With Explicit Array ON (default):
{ "items": { "item": [{ "name": ["Widget"] }] } }
With Explicit Array OFF:
{ "items": { "item": { "name": "Widget" } } }
The array format is safer because it handles both single and multiple elements consistently. Without it, a single <item> becomes an object but multiple <item> elements become an array, making your expressions unpredictable.
Explicit Root explained:
With Explicit Root ON (default):
{ "order": { "customer": ["John"] } }
With Explicit Root OFF:
{ "customer": ["John"] }
Turn this off when you only need the root element’s children and want cleaner access paths.
Merge Attributes explained:
With Merge Attributes OFF (default):
{ "product": { "$": { "id": "123" }, "name": ["Widget"] } }
With Merge Attributes ON:
{ "product": { "id": "123", "name": ["Widget"] } }
Merging makes access simpler ($json.product.id instead of $json.product.$.id) but can cause conflicts if an attribute has the same name as a child element.
JSON to XML Options
| Option | Default | Effect |
|---|---|---|
| Headless | Off | Omits XML declaration header |
| Root Name | - | Custom root element name |
| Cdata | Off | Wraps text in CDATA sections (use when text contains <, >, or &) |
| Attribute Key | $ | Key for attribute values |
| Character Key | _ | Key for text content |
Headless option:
With Headless OFF (default):
<?xml version="1.0" encoding="UTF-8"?>
<order>...</order>
With Headless ON:
<order>...</order>
Some APIs don’t accept the XML declaration. Enable Headless for those cases.
Root Name option:
When your JSON doesn’t have a single root property, specify one:
Input:
{ "name": "Widget", "price": 29.99 }
With Root Name set to product:
<product>
<name>Widget</name>
<price>29.99</price>
</product>
Working with XML Attributes
Attributes are the trickiest part of XML-JSON conversion. Here’s how to handle common patterns.
Reading Attributes from XML
After converting XML to JSON, attributes live under the $ key:
<user id="42" status="active" role="admin">
<name>John Doe</name>
<email>[email protected]</email>
</user>
Becomes:
{
"user": {
"$": {
"id": "42",
"status": "active",
"role": "admin"
},
"name": ["John Doe"],
"email": ["[email protected]"]
}
}
Access patterns:
// Get user ID
{{ $json.user.$.id }}
// Get all attributes as object
{{ $json.user.$ }}
// Check status
{{ $json.user.$.status === 'active' }}
Creating Attributes in XML
To generate XML with attributes, structure your JSON properly:
{
"invoice": {
"$": {
"number": "INV-2024-001",
"date": "2024-01-15"
},
"customer": {
"$": { "id": "CUST-123" },
"_": "Acme Corp"
},
"total": {
"$": { "currency": "USD" },
"_": "1500.00"
}
}
}
Output:
<invoice number="INV-2024-001" date="2024-01-15">
<customer id="CUST-123">Acme Corp</customer>
<total currency="USD">1500.00</total>
</invoice>
Handling Namespaced XML
XML namespaces appear as prefixed tags:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns1:GetUserResponse xmlns:ns1="http://example.com/users">
<ns1:User>John</ns1:User>
</ns1:GetUserResponse>
</soap:Body>
</soap:Envelope>
After conversion, namespace prefixes become part of the property names:
{
"soap:Envelope": {
"$": { "xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/" },
"soap:Body": [{
"ns1:GetUserResponse": [{
"$": { "xmlns:ns1": "http://example.com/users" },
"ns1:User": ["John"]
}]
}]
}
}
Access with bracket notation for colons:
{{ $json['soap:Envelope']['soap:Body'][0]['ns1:GetUserResponse'][0]['ns1:User'][0] }}
For cleaner access, use a Code node to strip namespace prefixes after conversion.
Real-World Examples
Example 1: Converting SOAP API Responses
Scenario: Integrate with a legacy inventory system using SOAP.
Workflow:
Manual Trigger → HTTP Request (SOAP) → XML to JSON → Edit Fields → Output
HTTP Request configuration:
- Method: POST
- URL:
https://legacy.example.com/soap/inventory - Headers:
Content-Type: text/xml; charset=utf-8 - Body (raw XML with SOAP envelope):
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetInventory xmlns="http://example.com/inventory">
<sku>WIDGET-001</sku>
</GetInventory>
</soap:Body>
</soap:Envelope>
XML Node:
- Mode: XML to JSON
- Property Name:
data - Explicit Array: On
- Merge Attributes: On (for cleaner namespace handling)
Edit Fields (extract the data you need):
// Access the response data
{{ $json['soap:Envelope']['soap:Body'][0]['GetInventoryResponse'][0] }}
For more on working with HTTP requests, see our HTTP Request node guide.
Example 2: Parsing RSS Feeds
Scenario: Aggregate content from multiple RSS feeds for a newsletter.
Workflow:
Schedule Trigger → HTTP Request (RSS) → XML to JSON → Split Items → Format → Merge
HTTP Request:
- URL:
https://blog.example.com/feed.xml - Method: GET
XML Node:
- Mode: XML to JSON
- Property Name:
data - Explicit Root: On
Code Node (extract articles):
const items = $input.all();
const feed = items[0].json;
// Navigate to the items in RSS structure
const channel = feed.rss.channel[0];
const articles = channel.item || [];
// Map to clean objects
return articles.map(article => ({
json: {
title: article.title?.[0] || '',
link: article.link?.[0] || '',
description: article.description?.[0] || '',
pubDate: article.pubDate?.[0] || ''
}
}));
Example 3: Creating XML for Legacy System
Scenario: Send order data to an ERP system that only accepts XML.
Workflow:
Webhook (order data) → Edit Fields (structure) → JSON to XML → HTTP Request (POST)
Edit Fields (structure data for XML):
{
"order": {
"$": {
"id": "{{ $json.orderId }}",
"date": "{{ $now.toISODate() }}"
},
"customer": {
"$": { "id": "{{ $json.customerId }}" },
"_": "{{ $json.customerName }}"
},
"items": {
"item": "{{ $json.items.map(i => ({ '$': { sku: i.sku }, name: i.name, qty: i.quantity })) }}"
},
"total": {
"$": { "currency": "USD" },
"_": "{{ $json.total }}"
}
}
}
XML Node:
- Mode: JSON to XML
- Property Name: The field containing structured JSON
- Headless: Off (include XML declaration)
HTTP Request:
- Method: POST
- URL:
https://erp.example.com/api/orders - Content-Type:
application/xml - Body:
{{ $json.data }}(the XML output)
Example 4: Processing XML Data Feeds
Scenario: Parse a product catalog XML file received via email or FTP.
Workflow:
Trigger → Read Binary File → Extract from File → XML to JSON → Loop → Save to DB
Extract from File:
- Operation: Text
- This converts binary file to text string
XML Node:
- Mode: XML to JSON
- Property Name:
data(from Extract from File) - Explicit Array: On
- Normalize: On (clean up whitespace)
Processing loop:
// The converted JSON structure
const catalog = $json.catalog;
const products = catalog.product || [];
// Process each product
return products.map(product => ({
json: {
sku: product.$.sku,
name: product.name[0],
price: parseFloat(product.price[0]),
category: product.category[0],
inStock: product.$.inStock === 'true'
}
}));
Example 5: Building XML Configuration
Scenario: Generate configuration XML from workflow data.
Workflow:
Manual Trigger → Set Config Data → JSON to XML → Write File
Set Node (config structure):
{
"configuration": {
"$": {
"version": "1.0",
"generated": "{{ $now.toISO() }}"
},
"database": {
"host": "{{ $vars.DB_HOST }}",
"port": "5432",
"name": "{{ $vars.DB_NAME }}"
},
"cache": {
"$": { "enabled": "true" },
"ttl": "3600",
"maxSize": "1000"
},
"features": {
"feature": [
{ "$": { "name": "darkMode", "enabled": "true" } },
{ "$": { "name": "beta", "enabled": "false" } }
]
}
}
}
XML Node:
- Mode: JSON to XML
- Headless: Off
Output:
<?xml version="1.0" encoding="UTF-8"?>
<configuration version="1.0" generated="2024-01-15T10:30:00Z">
<database>
<host>db.example.com</host>
<port>5432</port>
<name>production</name>
</database>
<cache enabled="true">
<ttl>3600</ttl>
<maxSize>1000</maxSize>
</cache>
<features>
<feature name="darkMode" enabled="true"/>
<feature name="beta" enabled="false"/>
</features>
</configuration>
Common Errors and Fixes
Error Reference Table
| Error | Cause | Solution |
|---|---|---|
| ”No property ‘data’ does not exist” | Wrong property name specified | Check the actual field name in your input data |
| ”Non-whitespace before first tag” | XML has content before opening tag | Remove any text/BOM before <?xml or <root> |
| ”Unexpected close tag” | Malformed XML structure | Validate XML with external tool first |
| ”Invalid character in name” | Special characters in attribute keys | Check Attribute Key setting matches your JSON |
| ”Cannot read property of undefined” | Accessing non-existent path | Verify JSON structure after conversion |
| ”Input must be a string” | Passing object instead of XML string | Ensure source is string, not already-parsed JSON |
Malformed XML Issues
The most common failure: XML validation differs between tools.
Online validators may be lenient, but n8n’s parser is strict. Common issues:
Missing closing tags:
<!-- Wrong -->
<item>
<name>Widget
<price>29.99</price>
</item>
<!-- Correct -->
<item>
<name>Widget</name>
<price>29.99</price>
</item>
Unclosed self-closing tags:
<!-- Wrong -->
<br>
<img src="image.jpg">
<!-- Correct -->
<br/>
<img src="image.jpg"/>
Invalid characters:
<!-- Wrong -->
<description>Price < $50 & new</description>
<!-- Correct -->
<description>Price < $50 & new</description>
Solution: Use an XML validator before processing. Add a Code node to clean/validate XML if your source is unreliable.
Binary Data Confusion
Problem: XML is in a file (binary) but you’re trying to convert directly.
Symptoms:
- Conversion returns empty or errors
- Data shows as buffer/binary
Solution: Use Extract from File node first:
- Add Extract from File node
- Set Operation to “Text”
- Connect to XML node
- XML node converts the extracted text
For complete guidance on binary data workflows, see our binary data handling guide.
Attribute Access Failures
Problem: Can’t access attributes after conversion.
Check these:
-
Verify Attribute Key setting - Default is
$. If you changed it, access with that key. -
Check Ignore Attributes - If enabled, attributes are discarded.
-
Array wrapping - With Explicit Array on, even single values are arrays:
// Wrong
{{ $json.product.$.id }}
// May need array index
{{ $json.product[0].$.id }}
- Merge Attributes - If enabled, attributes merge with children:
// Without merge
{{ $json.product.$.id }}
// With merge
{{ $json.product.id }}
For debugging expressions, try our expression validator tool.
Pro Tips and Best Practices
1. Validate XML Before Processing
Save debugging time by validating XML upfront:
// In a Code node before XML conversion
const xml = $json.data;
// Basic validation checks
if (!xml || typeof xml !== 'string') {
throw new Error('Input is not a string');
}
if (!xml.trim().startsWith('<')) {
throw new Error('Content does not appear to be XML');
}
return [{ json: { data: xml } }];
2. Keep Explicit Array Enabled
The default Explicit Array setting prevents expression errors. Without it, your access pattern depends on whether there’s one or multiple elements:
// Inconsistent without Explicit Array:
$json.items.item.name // Works for single item
$json.items.item[0].name // Required for multiple
// Consistent with Explicit Array:
$json.items.item[0].name // Always works
3. Use Code Node for Complex Transformations
When XML structures don’t map cleanly to your needs:
const items = $input.all();
const xmlData = items[0].json;
// Flatten nested structure
function flattenXml(obj, prefix = '') {
let result = {};
for (const key of Object.keys(obj)) {
if (key === '$' || key === '_') continue;
const value = obj[key];
const newKey = prefix ? `${prefix}_${key}` : key;
if (Array.isArray(value)) {
value.forEach((v, i) => {
if (typeof v === 'object') {
Object.assign(result, flattenXml(v, `${newKey}_${i}`));
} else {
result[`${newKey}_${i}`] = v;
}
});
} else if (typeof value === 'object') {
Object.assign(result, flattenXml(value, newKey));
} else {
result[newKey] = value;
}
}
return result;
}
return [{ json: flattenXml(xmlData) }];
4. Handle SOAP Namespaces Gracefully
Strip namespace prefixes for cleaner data:
// Remove namespace prefixes from keys
function stripNamespaces(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
if (Array.isArray(obj)) {
return obj.map(stripNamespaces);
}
const result = {};
for (const key of Object.keys(obj)) {
const newKey = key.includes(':') ? key.split(':')[1] : key;
result[newKey] = stripNamespaces(obj[key]);
}
return result;
}
5. Test with Real Data Early
XML from documentation examples often differs from production data. Test with actual API responses or files as soon as possible to catch structural differences.
6. Use Merge Attributes Cautiously
While Merge Attributes simplifies access, it can cause issues:
- Attribute names might conflict with element names
- Code relying on
$structure breaks - Harder to distinguish attributes from children
Keep it off unless you’re certain there are no naming conflicts.
7. Handle Empty Elements
XML empty elements can become unexpected values:
<product>
<description></description>
<notes/>
</product>
May become:
{
"product": {
"description": [""],
"notes": [""]
}
}
Add defensive checks in your expressions:
{{ $json.product.description[0] || 'No description' }}
8. Combine with Rate Limiting for APIs
When fetching XML from external APIs, respect rate limits. See our API rate limiting guide for strategies.
When to Get Help
Some XML integration scenarios require specialized expertise:
- Complex SOAP services with multiple WSDLs and authentication
- High-volume XML processing requiring optimization
- XML schema validation before processing
- Custom XML transformations with XSLT-like requirements
- Legacy system integration with undocumented XML formats
Our workflow development services can build production-ready XML integrations. For architectural guidance on complex data pipelines, explore our n8n consulting services.
For workflow debugging and testing, use our workflow debugger tool.
Frequently Asked Questions
Why does my XML to JSON conversion fail when online validators say the XML is valid?
Online validators often accept “mostly valid” XML that strict parsers reject. The XML node uses a strict parser that enforces proper structure. Common differences:
-
Character encoding issues - The XML may contain invisible BOM (Byte Order Mark) characters or encoding mismatches. Try trimming the input or removing the first few characters.
-
Missing closing tags - Visual validators sometimes auto-complete tags. Check every opening tag has a matching closer.
-
Unescaped special characters - Characters like
<,>,&inside text must be escaped (<,>,&). Validators may display them correctly even when malformed. -
Invalid attribute values - Quotes in attribute values must be escaped or use alternate quote styles.
Debugging approach: Copy the XML from your n8n output (not from the original source), paste into a strict validator, and look for the specific error. The W3C XML Validator is stricter than most online tools.
How do I handle XML with namespaces like xmlns or SOAP envelopes?
Namespaces become part of property names in the JSON output. A SOAP response like <soap:Body> converts to a property named soap:Body.
Access namespaced properties:
// Bracket notation required for colons
{{ $json['soap:Envelope']['soap:Body'][0] }}
// Or in Code node
const body = $json['soap:Envelope']['soap:Body'][0];
Strip namespaces for cleaner access:
// In a Code node after XML conversion
function stripNS(obj) {
if (!obj || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(stripNS);
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [
k.includes(':') ? k.split(':').pop() : k,
stripNS(v)
])
);
}
return [{ json: stripNS($json) }];
This converts soap:Body to just Body, making expressions simpler.
What’s the difference between Attribute Key and Character Key settings?
These settings control how the node represents XML attributes and text content in JSON:
Attribute Key (default: $) - Where attributes appear:
<product id="123" active="true">Widget</product>
Becomes:
{ "product": { "$": { "id": "123", "active": "true" }, "_": "Widget" } }
The $ key contains all attributes as an object.
Character Key (default: _) - Where text content appears when an element has both attributes and text:
{ "product": { "$": { ... }, "_": "Widget" } }
The _ key holds the actual text content.
When elements have only text (no attributes):
<name>Widget</name>
Becomes simply:
{ "name": ["Widget"] }
No $ or _ keys needed because there are no attributes.
Changing these keys: Only change them if your JSON data already uses $ or _ for other purposes and you need to avoid conflicts.
How do I work with SOAP APIs in n8n?
SOAP APIs use XML for both requests and responses. Here’s the complete pattern:
1. Create the SOAP request:
// In a Code or Set node
const soapEnvelope = `<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUser xmlns="http://example.com/users">
<userId>${$json.userId}</userId>
</GetUser>
</soap:Body>
</soap:Envelope>`;
return [{ json: { soapRequest: soapEnvelope } }];
2. Send via HTTP Request:
- Method: POST
- URL: Your SOAP endpoint
- Headers:
Content-Type:text/xml; charset=utf-8SOAPAction:"http://example.com/users/GetUser"(check WSDL)
- Body:
{{ $json.soapRequest }}
3. Convert response with XML node:
- Mode: XML to JSON
- Property Name:
data - Merge Attributes: On (optional, simplifies namespace access)
4. Extract the data you need:
// Navigate through SOAP structure
const response = $json['soap:Envelope']['soap:Body'][0];
const userData = response['GetUserResponse'][0]['User'][0];
For complex SOAP integrations with authentication and multiple operations, consider our workflow development services.
Can I convert JSON with attributes back to properly formatted XML?
Yes, but your JSON must follow the attribute/character key conventions. Here’s how to structure JSON for XML output with attributes:
Element with attributes only:
{
"product": {
"$": { "id": "123", "status": "active" },
"name": "Widget",
"price": "29.99"
}
}
Output:
<product id="123" status="active">
<name>Widget</name>
<price>29.99</price>
</product>
Element with attributes AND text content:
{
"price": {
"$": { "currency": "USD" },
"_": "29.99"
}
}
Output:
<price currency="USD">29.99</price>
Common mistake: Trying to add attributes without the $ key structure:
// Wrong - this creates child elements, not attributes
{ "product": { "id": "123", "name": "Widget" } }
// Correct - uses $ for attributes
{ "product": { "$": { "id": "123" }, "name": "Widget" } }
Building the structure dynamically:
// In Edit Fields or Code node
return [{
json: {
invoice: {
'$': {
number: $json.invoiceNumber,
date: $now.toISODate()
},
customer: $json.customerName,
items: {
item: $json.items.map(i => ({
'$': { sku: i.sku },
'_': i.name
}))
}
}
}
}];
This produces properly attributed XML that legacy systems expect.
How do I process XML received via webhook?
When external systems POST XML to your workflow:
Webhook configuration:
- Set HTTP Method to POST
- The XML arrives in the request body
Processing flow:
Webhook → XML to JSON → Process Data → Response
Access the XML:
// XML is typically in $json.body or $json.data
// Check your webhook output to find the exact field
{{ $json.body }}
Important: If the webhook receives the XML as a string, the XML node converts it directly. If it arrives as binary data, add an Extract from File node first. See our binary data handling guide for details on working with different data formats.