Package Exports
- @stacksjs/ts-xml
- @stacksjs/ts-xml/builder.d.ts
- @stacksjs/ts-xml/entities.d.ts
- @stacksjs/ts-xml/index.d.ts
- @stacksjs/ts-xml/index.js
- @stacksjs/ts-xml/parser.d.ts
- @stacksjs/ts-xml/types.d.ts
- @stacksjs/ts-xml/validator.d.ts
Readme

ts-xml
A fast, dependency-free XML parser, builder, and validator for TypeScript and Bun. Character-by-character parsing with charCodeAt comparisons for maximum performance.
Features
- Zero Dependencies - only Bun as a runtime
- XMLParser - parse XML strings to JavaScript objects
- XMLBuilder - build XML strings from JavaScript objects
- XMLValidator - validate XML structure with detailed error reporting
- Entity Handling - XML, HTML, numeric, and hex entity decoding/encoding
- Namespace Support - optional namespace prefix removal
- Preserve Order - array-based output to maintain element ordering
- Stop Nodes - skip parsing of specific tag contents
- Unpaired Tags - support for HTML-style void elements (e.g.,
<br>,<hr>) - CDATA & Comments - optionally capture CDATA sections and comments as properties
- Processing Instructions - capture
<?xml?>declarations and custom PIs - Value Parsing - automatic number, boolean, hex, and scientific notation parsing
- Custom Processors - tag value, attribute value, and tag name transformation callbacks
- Fully Typed - complete TypeScript type definitions
Get Started
Installation
bun install ts-xmlParsing XML
import { XMLParser } from 'ts-xml'
const parser = new XMLParser()
const result = parser.parse('<root><item>Hello</item></root>')
// { root: { item: 'Hello' } }Parsing with Attributes
import { XMLParser } from 'ts-xml'
const parser = new XMLParser({ ignoreAttributes: false })
const result = parser.parse('<book isbn="978-0-123"><title>XML Guide</title></book>')
// { book: { '@_isbn': '978-0-123', title: 'XML Guide' } }Building XML
import { XMLBuilder } from 'ts-xml'
const builder = new XMLBuilder({ format: true, indentBy: ' ' })
const xml = builder.build({
root: {
item: ['one', 'two', 'three'],
},
})Validating XML
import { XMLValidator } from 'ts-xml'
const result = XMLValidator('<root><child/></root>')
if (result === true) {
console.log('Valid XML')
}
else {
console.log(`Error: ${result.err.msg} at line ${result.err.line}`)
}Preserve Element Order
import { XMLParser, XMLBuilder } from 'ts-xml'
const parser = new XMLParser({ preserveOrder: true })
const ordered = parser.parse('<root><a>1</a><b>2</b><a>3</a></root>')
// Maintains original element order as arrays
const builder = new XMLBuilder({ preserveOrder: true })
const xml = builder.build(ordered) // Round-trips correctlyConfiguration
Parser Options
| Option | Type | Default | Description |
|---|---|---|---|
attributeNamePrefix |
string |
"@_" |
Prefix for attribute names |
attributesGroupName |
string | false |
false |
Group attributes under this key |
textNodeName |
string |
"#text" |
Property name for text content |
ignoreAttributes |
boolean |
true |
Skip attribute parsing |
removeNSPrefix |
boolean |
false |
Strip namespace prefixes |
allowBooleanAttributes |
boolean |
false |
Allow attributes without values |
alwaysCreateTextNode |
boolean |
false |
Always create text node property |
trimValues |
boolean |
true |
Trim whitespace from values |
parseTagValue |
boolean |
true |
Parse numbers/booleans from text |
parseAttributeValue |
boolean |
false |
Parse numbers/booleans from attributes |
processEntities |
boolean |
true |
Decode XML entities |
htmlEntities |
boolean |
false |
Decode HTML entities |
commentPropName |
string | false |
false |
Property name for comments |
cdataPropName |
string | false |
false |
Property name for CDATA sections |
piPropName |
string | false |
false |
Property name for processing instructions |
preserveOrder |
boolean |
false |
Maintain element ordering |
stopNodes |
string[] |
[] |
Tags whose content is not parsed |
unpairedTags |
string[] |
[] |
Tags that don't need closing |
numberParseOptions |
NumberParseOptions |
{ hex: true, leadingZeros: true, scientific: true } |
Number parsing behavior |
Builder Options
| Option | Type | Default | Description |
|---|---|---|---|
attributeNamePrefix |
string |
"@_" |
Prefix for attribute names |
textNodeName |
string |
"#text" |
Property name for text content |
ignoreAttributes |
boolean |
false |
Skip attributes when building |
format |
boolean |
false |
Pretty-print output |
indentBy |
string |
" " |
Indentation string |
suppressEmptyNode |
boolean |
false |
Render empty nodes as self-closing |
suppressBooleanAttributes |
boolean |
true |
Render boolean attributes without ="true" |
processEntities |
boolean |
true |
Encode entities in output |
preserveOrder |
boolean |
false |
Build from ordered format |
Benchmarks
Benchmarked on Apple M3 Pro using mitata, comparing against fast-xml-parser, xml2js, and sax.
Parsing
| Benchmark | ts-xml | fast-xml-parser | xml2js | sax |
|---|---|---|---|---|
| Simple XML | 344 ns | 1.64 µs (4.8x slower) | 2.03 µs | 885 ns |
| Medium (3 products + attrs) | 6.67 µs | 27.4 µs (4.1x slower) | 18.0 µs | 12.3 µs |
| Large (100 products) | 321 µs | 1.16 ms (3.6x slower) | 2.04 ms | 1.88 ms |
| Very Large (1000 products) | 3.02 ms | 13.0 ms (4.3x slower) | 9.83 ms | 4.90 ms |
| CDATA | 831 ns | 2.89 µs (3.5x slower) | 5.62 µs | 5.36 µs |
| Deep nesting (50 levels) | 9.33 µs | 59.1 µs (6.3x slower) | 43.0 µs | 27.5 µs |
| RSS Feed | 8.33 µs | 40.4 µs (4.9x slower) | 53.9 µs | 28.7 µs |
| Entities | 2.28 µs | 6.05 µs (2.7x slower) | 9.42 µs | 6.42 µs |
| Namespaces | 3.42 µs | 14.4 µs (4.2x slower) | 11.6 µs | 7.91 µs |
Building
| Benchmark | ts-xml | fast-xml-parser | xml2js |
|---|---|---|---|
| Small object | 1.61 µs | 3.83 µs (2.4x slower) | 8.60 µs |
| Large (100 products) | 42.6 µs | 118 µs (2.8x slower) | 197 µs |
| Formatted output | 1.67 µs | 4.07 µs (2.4x slower) | 8.05 µs |
Validation
| Benchmark | ts-xml | fast-xml-parser |
|---|---|---|
| Valid XML | 3.30 µs | 7.79 µs (2.4x slower) |
| Large valid (1000 products) | 1.39 ms | 3.37 ms (2.4x slower) |
| Invalid XML (early exit) | 260 ns | 591 ns (2.3x slower) |
Round-trip
| Benchmark | ts-xml | fast-xml-parser |
|---|---|---|
| Parse + Build (medium) | 9.15 µs | 34.7 µs (3.8x slower) |
Run benchmarks yourself:
bun run bench
Testing
bun test724 tests across 6 test files covering parser, builder, validator, entities, and edge cases.
Changelog
Please see our releases page for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using this package:
Join the Stacks Discord Server
Postcardware
"Software that is free, but hopes for a postcard." We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎
Sponsors
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
License
The MIT License (MIT). Please see LICENSE for more information.
Made with 💙