High-performance XML Schema (XSD) validation for JavaScript, powered by Apache Xerces-C++ compiled to WebAssembly.
Installation · Quick Start · API Reference · Benchmarks · Architecture · Playground
xerces-wasm brings the validation engine of Apache Xerces-C++ to Node.js and the browser. Schemas are compiled once into an in-memory grammar pool and reused across every subsequent validation, which removes the per-call schema parsing cost that dominates most WebAssembly XML validators.
- Reference-grade validation — the same Xerces-C++ engine used across the industry, with full XSD 1.0 support.
- Compile once, validate many — schemas are cached in WebAssembly memory and reused on every call.
- Modular schemas — native support for
xs:includeandxs:importacross multiple files. - Project isolation — each validator owns its own grammar pool, so identical XML can be valid in one project and invalid in another.
- Structured diagnostics — separate well-formedness and schema errors, each with line, column, and severity.
- Typed API — ships with TypeScript declarations.
npm install xerces-wasmRequires Node.js 18 or later.
import { createProjectValidator } from "xerces-wasm";
// 1. Compile the schemas once. The grammar pool is cached in WASM memory.
const validator = await createProjectValidator({
entry: "main.xsd",
files, // Record<string, string>: { filename: xsdText }
});
// 2. Validate as many documents as needed against the cached pool.
const result = await validator.validate(`<log level="full"/>`);
if (!result.valid) {
for (const e of [...result.parseErrors, ...result.schemaErrors]) {
console.error(`${e.line}:${e.column} [${e.severity}] ${e.message}`);
}
}
// 3. Release the native allocations when the validator is no longer needed.
validator.destroy();Compiles a set of XSD files into a reusable validator. Recommended for any workload that validates more than one document.
| Option | Type | Description |
|---|---|---|
entry |
string |
Root XSD filename. Must be a key of files. |
files |
Record<string, XmlInput> |
All schema files, keyed by bare filename. |
targetNamespace |
string (optional) |
Target namespace. Detected from the entry schema if omitted. |
Returns a ProjectValidator:
| Method | Description |
|---|---|
validate(xml) |
Validates a document against the cached grammar pool. Returns Promise<ValidationResult>. |
reload(files) |
Recompiles the grammar pool from an updated set of schema files. |
destroy() |
Frees the grammar pool and all associated WASM memory. |
One-off validation. The schema is parsed on every call, so prefer createProjectValidator for repeated use.
xsd accepts a single schema or a bundle: { entry, imports? }.
Convenience wrapper that reads the XML and XSD from disk before validating.
interface ValidationResult {
valid: boolean;
parseErrors: Diagnostic[]; // well-formedness errors
schemaErrors: Diagnostic[]; // XSD rule violations
}
interface Diagnostic {
message: string;
line: number;
column: number;
severity: "warning" | "error" | "fatal";
}XmlInput accepts string, Buffer, Blob, or File.
Stateless validators re-parse the schema on every call. xerces-wasm parses it once, so per-document cost is limited to validation itself.
The results below compare xerces-wasm with xmllint-wasm, measured on an Apple M4 with Node.js v22 over five interleaved trials.
| Scenario | xmllint-wasm |
xerces-wasm |
Improvement |
|---|---|---|---|
| Single schema, warm loop (1,000 runs) | 22,908 ms | 41.7 ms | 549× |
| Multi-file modular schema (4 XSDs) | 23,916 ms | 69.1 ms | 346× |
| Small payload latency (1.2 KB) | 23.19 ms / call | 0.06 ms / call | 362× |
| Medium payload throughput (100 KB) | 3.59 MB/s | 34.70 MB/s | 9.6× |
| Schema-invalid XML | 24.59 ms / call | 0.07 ms / call | 355× |
| Memory delta (1,000 runs) | +2.25 MB | +0.41 MB | ~5.5× lower |
50 parallel validations (Promise.all) |
221.58 ms | 3.57 ms | 62× |
Methodology and reproducible code: xml-val-benchmark.
- Best fit: services that validate many documents against a fixed set of schemas, such as APIs, message pipelines, and editor tooling.
- Large documents: for payloads above roughly 5 MB, validation becomes compute-bound and streaming validators can reach higher throughput.
- Threading: validation runs synchronously on the calling thread. For batches of multi-megabyte documents, run validation in a worker thread or background queue.
Xerces-C++ validation has two distinct phases: compiling schemas, which is expensive, and validating documents, which is fast.
- Schema compilation (one time). XSD files are parsed and compiled into DFA structures, stored in an
XMLGrammarPool. - Validation (per document). The XML input is streamed through the pre-compiled grammar using a transient
SAXParser.
xerces-wasm separates these concerns explicitly:
- Persistent state. Each project compiles its schemas once into a locked
XMLGrammarPoolin the WASM heap, isolated from all other projects. - Transient engine. Every
validate()call creates a disposableSAXParser, attaches it to the project's pool, validates, and destroys itself.
An interactive browser playground is available for trying the validator without installing anything. Source: xerces-playground.
Requires Git, Node.js, and an internet connection. Emscripten and Xerces-C++ are fetched automatically.
git clone --recurse-submodules https://github.com/harshanacz/xerces-wasm-validator
cd xerces-wasm-validator
npm install
npm run build:wasm # Compile Xerces-C++ to wasm/xerces_validator.{js,wasm}
npm run build:ts # Compile TypeScript to dist/
npm testFor reproducible WASM builds, see docs/reproducible-wasm-build.md. Release history is tracked in the changelog.
Released under the MIT License.
This package includes Apache Xerces-C++, licensed under Apache-2.0. See LICENSE-APACHE and NOTICE.


