xml-js, xmlbuilder, and xmlbuilder2 are essential tools for working with XML in JavaScript environments, but they solve different problems. xml-js focuses on converting XML data to JSON objects and vice versa, making it ideal for data interchange and configuration parsing. xmlbuilder and xmlbuilder2 are designed for programmatically generating XML documents from scratch using a chainable API. While xmlbuilder is the classic, stable implementation, xmlbuilder2 is its modern successor, offering improved performance, streaming support for large files, and better compliance with modern JavaScript standards.
Working with XML in JavaScript often falls into two distinct categories: converting existing XML data into usable objects (parsing) or creating new XML documents from scratch (generation). xml-js handles the former, while xmlbuilder and xmlbuilder2 handle the latter. Understanding these distinctions is critical for architectural decisions, as using a generator for parsing tasks (or vice versa) leads to unnecessary complexity.
xml-js is a utility for data transformation.
// xml-js: Convert XML string to JS object
const convert = require('xml-js');
const xml = '<note><to>Tove</to><from>Jani</from></note>';
const result = convert.xml2js(xml, { compact: true });
// Output: { note: { to: { _text: 'Tove' }, from: { _text: 'Jani' } } }
xmlbuilder is a tree builder for generation.
.end().// xmlbuilder: Create XML tree
const builder = require('xmlbuilder');
const root = builder.create('note')
.ele('to').txt('Tove').up()
.ele('from').txt('Jani').up();
const xml = root.end({ pretty: true });
xmlbuilder2 is the modern tree builder.
xmlbuilder but with streaming support.// xmlbuilder2: Create XML with streaming capability
const { create } = require('xmlbuilder2');
const root = create().ele('note');
root.ele('to').txt('Tove');
root.ele('from').txt('Jani');
const xml = root.end({ prettyPrint: true });
Memory management is the biggest differentiator between the classic and modern builders.
xml-js loads the entire XML string into memory to parse it.
// xml-js: Synchronous parsing (blocks event loop)
const result = convert.xml2js(largeXmlString);
xmlbuilder builds the full document in memory before outputting.
// xmlbuilder: In-memory construction
const doc = builder.create('root');
// ... add thousands of nodes ...
const output = doc.end(); // All in memory
xmlbuilder2 supports streaming output.
// xmlbuilder2: Stream to file
const { create } = require('xmlbuilder2');
const root = create().ele('root');
const writer = root.write({ prettyPrint: true });
writer.pipe(require('fs').createWriteStream('output.xml'));
The way you interact with these libraries affects code readability and maintenance.
xml-js uses a functional API.
// xml-js: Functional style
const json = convert.xml2js(xml, { compact: true, spaces: 2 });
const xmlBack = convert.js2xml(json, { compact: true, spaces: 2 });
xmlbuilder uses a chainable, mutable API.
.up() is required to move back to the parent node.// xmlbuilder: Chainable with .up()
builder.create('book')
.ele('title').txt('Design Patterns').up()
.ele('author').txt('Gang of Four').up();
xmlbuilder2 uses a refined chainable API.
xmlbuilder but more intuitive.// xmlbuilder2: Chainable with namespace support
const doc = create().ele('http://example.com/ns', 'book');
doc.att('id', '123').ele('title').txt('Modern XML');
You need to read a sitemap.xml to extract URLs for a crawler.
xml-js// xml-js: Parse sitemap
const sitemap = convert.xml2js(fs.readFileSync('sitemap.xml'));
const urls = sitemap.urlset.url.map(u => u.loc);
You need to construct a specific XML payload for a legacy enterprise API.
xmlbuilder2// xmlbuilder2: SOAP envelope
const root = create().ele('soap:Envelope').att('xmlns:soap', '...');
root.ele('soap:Body').ele('GetUser').ele('UserID').txt('101');
const payload = root.end();
You need to export millions of log entries into a single XML archive file.
xmlbuilder2 (Streaming)xmlbuilder would crash due to memory limits; xml-js is for parsing, not generation.// xmlbuilder2: Stream large data
const writer = create().ele('logs').write({ prettyPrint: false });
logs.forEach(log => writer.ele('entry').txt(log));
writer.end();
Your build tool reads a simple config.xml file to set environment variables.
xml-js// xml-js: Read config
const config = convert.xml2js(fs.readFileSync('config.xml'), { compact: true });
const env = config.config.environment._text;
| Feature | xml-js | xmlbuilder | xmlbuilder2 |
|---|---|---|---|
| Primary Use | XML β JSON Conversion | XML Generation | XML Generation |
| API Style | Functional | Chainable | Chainable |
| Memory Model | In-Memory (Buffer) | In-Memory (Buffer) | Streaming Supported |
| Dependencies | None | None | Minimal |
| Namespace Support | Basic | Yes | Advanced |
| Maintenance | Active | Stable (Legacy) | Active (Modern) |
Think in terms of data flow direction:
xml-js. It turns XML into plain JavaScript objects you can work with easily. It is lightweight and perfect for configuration or API data.xmlbuilder2. It is the modern standard for generation. It avoids the memory pitfalls of the original xmlbuilder and supports streaming, which is essential for scalability.xmlbuilder is still stable and works fine for small tasks, but plan to migrate to xmlbuilder2 for long-term health.Final Thought: Don't mix tools. Use xml-js for parsing data and xmlbuilder2 for creating documents. This separation keeps your architecture clean and performant.
Choose xmlbuilder2 for new projects that require generating XML documents, especially if you are dealing with large datasets. It supports streaming, which prevents memory overload when creating massive files. Its API is similar to xmlbuilder but modernized, offering better error handling and alignment with current web standards. It is the recommended choice for forward-looking architectures.
Choose xml-js when your primary goal is data conversion between XML and JSON formats. It is the best fit for parsing API responses, reading configuration files, or transforming data structures without needing to manipulate the XML DOM directly. It is lightweight and has no dependencies, making it suitable for both Node.js and browser environments where bundle size matters.
Choose xmlbuilder if you are maintaining a legacy codebase that already depends on it or if you need a proven, stable library for generating small to medium-sized XML documents. It offers a simple, chainable API that is easy to learn. However, for new projects requiring high performance or streaming capabilities, you should evaluate xmlbuilder2 instead.
An XML builder for node.js.
npm install xmlbuilder2
See: https://oozcitak.github.io/xmlbuilder2/
xmlbuilder2 is a wrapper around DOM nodes which adds chainable functions to make it easier to create and work with XML documents. For example the following XML document:
<?xml version="1.0"?>
<root att="val">
<foo>
<bar>foobar</bar>
</foo>
<baz/>
</root>
can be created with the following function chain:
const { create } = require('xmlbuilder2');
const root = create({ version: '1.0' })
.ele('root', { att: 'val' })
.ele('foo')
.ele('bar').txt('foobar').up()
.up()
.ele('baz').up()
.up();
// convert the XML tree to string
const xml = root.end({ prettyPrint: true });
console.log(xml);
The same XML document can be created by converting a JS object into XML nodes:
const { create } = require('xmlbuilder2');
const obj = {
root: {
'@att': 'val',
foo: {
bar: 'foobar'
},
baz: {}
}
};
const doc = create(obj);
const xml = doc.end({ prettyPrint: true });
console.log(xml);
xmlbuilder2 can also parse and serialize XML documents from different formats:
const { create } = require('xmlbuilder2');
const xmlStr = '<root att="val"><foo><bar>foobar</bar></foo></root>';
const doc = create(xmlStr);
// append a 'baz' element to the root node of the document
doc.root().ele('baz');
const xml = doc.end({ prettyPrint: true });
console.log(xml);
which would output the same document string at the top of this page.
Or you could return a JS object by changing the format argument to 'object':
const obj = doc.end({ format: 'object' });
console.log(obj);
{
root: {
'@att': 'val',
foo: {
bar: 'foobar'
},
baz: {}
}
}
You can convert between formats in one go with the convert function:
const { convert } = require('xmlbuilder2');
const xmlStr = '<root att="val"><foo><bar>foobar</bar></foo></root>';
const obj = convert(xmlStr, { format: "object" });
console.log(obj);
{
root: {
'@att': 'val',
foo: {
bar: 'foobar'
}
}
}
If you need to do some processing:
const { create } = require('xmlbuilder2');
const root = create().ele('squares');
root.com('f(x) = x^2');
for(let i = 1; i <= 5; i++)
{
const item = root.ele('data');
item.att('x', i);
item.att('y', i * i);
}
const xml = root.end({ prettyPrint: true });
console.log(xml);
This will result in:
<?xml version="1.0"?>
<squares>
<!-- f(x) = x^2 -->
<data x="1" y="1"/>
<data x="2" y="4"/>
<data x="3" y="9"/>
<data x="4" y="16"/>
<data x="5" y="25"/>
</squares>
You can build the minified production bundle (lib/xmlbuilder2.min.js) after cloning the repository and issuing npx webpack in your terminal. The bundle is also in the npm package, so you can also use a public npm CDN like jsDelivr or unpkg:
<!-- latest version from jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/xmlbuilder2/lib/xmlbuilder2.min.js"></script>
<!-- latest version from unpkg -->
<script src="https://unpkg.com/xmlbuilder2/lib/xmlbuilder2.min.js"></script>
Please consider becoming a sponsor to help support development.