XSD Validator
Validate against an XSD. Both files stay in your browser.
Everything runs in this tab. Nothing you paste is uploaded, logged or sent anywhere. Open your network panel and check.
Paste the document on the left, its XSD on the right, and press Validate against schema. libxml2, compiled to WebAssembly, runs the check in a Web Worker in this tab, and every violation is listed with a line, a column and a marker you can click to jump to it.
You usually get here after something else rejected the document: a partner returns cvc-complex-type.2.4.a, a build step fails on a config file, a gateway says the payload does not match the schema and stops there. You have the XSD and want the failing element, not a Java toolchain.
The difference is where it runs. Browsers have no native schema validator, so the free tools that offer XSD validation post both files to a server; the best known makes you submit the XML first and the schema on a second form. Neither file leaves this tab, and the engine, about 480 KB gzipped, is fetched when you press the button rather than on page load.
Well-formed first, then valid
These are two checks, not one. Well-formedness is syntax: closed tags, correct nesting, one root, escaped ampersands. It needs only the document, so it runs as you type. Validity is well-formedness plus conformance to a schema, which needs a second file, so it is a deliberate action.
The engine parses the XSD, compiles it, parses the document, then validates, and each step fails differently. A malformed document is never checked against a schema, because there is nothing coherent to check. Every diagnostic is labelled Schema problem or Validity error, and a position inside the schema is deliberately not clickable, since that line number belongs to the other pane.
XSD 1.0 only, and what that rules out
libxml2 implements XSD 1.0. XSD 1.1 became a W3C Recommendation on 5 April 2012 and is implemented by Apache Xerces-J, by Saxon-EE and by the Python xmlschema package, but not by libxml2, .NET's XmlSchemaSet or lxml, which wraps libxml2. A schema using 1.1 features will not compile here, and the panel names 1.1 as the likely reason rather than reporting a puzzling structural error.
- xs:assert and the xs:assertion facet: co-occurrence constraints, where one field's validity depends on another. XSD 1.0 cannot express these at all.
- xs:alternative, conditional type assignment, where an element's type is chosen by an XPath test on its own attributes.
- xs:openContent, xs:defaultOpenContent and xs:override, plus the 1.1 datatypes xs:dateTimeStamp, xs:dayTimeDuration and xs:yearMonthDuration. xs:precisionDecimal, which turns up in a lot of blog posts, was dropped before the final Recommendation.
- Two libxml2 limits on top: xs:redefine has long hit an unimplemented path, and xs:import cannot resolve a schemaLocation here, because nothing in this build may read a file. Flatten a multi-document schema set first.
The failures you will actually see
Ordering causes most of them. xs:sequence means the children must appear in the declared order, and that order is part of the contract rather than a formatting preference, so moving the element is usually the fix. XSD 1.0 allows xs:all, the order-free alternative, only at the top of a content model with each element at most once, which is why most real schemas use a sequence.
Namespaces cause the rest. If the schema declares targetNamespace="urn:example:orders", every element it governs must be in that namespace; if it declares none, they must be in no namespace, and adding a default xmlns breaks it. The related trap is elementFormDefault: absent, it means unqualified, so the root is qualified and the children must not be.
- cvc-complex-type.2.4.a: an element turned up where the schema did not allow it. The names in braces are what would have been legal, so "One of {qty} is expected" means qty was due next.
- cvc-complex-type.2.4.b: the content model was not satisfied, usually a required child missing at the end.
- cvc-datatype-valid.1.2.1: the text is not lexically valid for its type. An empty element declared xs:int, a dateTime without seconds, a decimal with a thousands separator.
- cvc-elt.1: no declaration found for the root element. Almost always a targetNamespace mismatch rather than a missing declaration.
- cvc-complex-type.3.2.2: an attribute is not allowed, often xsi:type or xsi:nil without the XMLSchema-instance namespace declared.
What it never does, and what it costs
It never fetches xsi:schemaLocation. XSD Part 1 calls that attribute a hint "as to the physical location of schema documents" and says a processor "should attempt to dereference" it "unless directed otherwise, for example by the invoking application". Declining is conformant, and common: SQL Server ignores it on xml-typed columns.
It could not fetch anything if it wanted to. Every parse uses XML_PARSE_NO_XXE, NONET and NO_SYS_CATALOG, and this build registers no resource loader, so external entities, an external DTD subset and a schemaLocation URL all resolve to nothing. XML_PARSE_HUGE is never set, which keeps libxml2's ceilings on: element depth 256, entity nesting 20 and an amplification cap.
One honest weakness: libxml2's documentation calls its schema module a partial implementation of XML Schema Part 1, and it often reports fewer diagnostics than Xerces on the same inputs. Read the list as "at least these" and re-run after each fix.
Validating against an XSD in code
The same check in the languages that consume XML. Every sample turns off external access, because a schema reference is a fetch instruction and most of these libraries follow one by default.
// npm i libxml2-wasm, the same engine this page runs.
import { XmlDocument, XsdValidator, ParseOption, XmlValidateError } from 'libxml2-wasm';
// NO_XXE blocks external DTDs and entities. Leaving HUGE unset is what keeps
// libxml2's depth, entity-nesting and amplification limits switched on.
const SAFE = ParseOption.XML_PARSE_NO_XXE | ParseOption.XML_PARSE_NONET;
export function validateXsd(xmlText, xsdText) {
let xsd = null;
let validator = null;
let doc = null;
try {
xsd = XmlDocument.fromString(xsdText, { option: SAFE });
validator = XsdValidator.fromDoc(xsd);
doc = XmlDocument.fromString(xmlText, { option: SAFE });
validator.validate(doc);
return { valid: true, errors: [] };
} catch (err) {
if (err instanceof XmlValidateError) {
// Each detail is { line, col, level, message, xpath }.
return { valid: false, errors: err.details };
}
throw err;
} finally {
// dispose() is mandatory: these are WebAssembly heap allocations.
doc?.dispose();
validator?.dispose();
xsd?.dispose();
}
}# pip install lxml
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False, # no entity substitution
no_network=True, # never fetch a schema or DTD
load_dtd=False,
huge_tree=False, # keep the depth and expansion limits
)
schema = etree.XMLSchema(etree.parse('schema.xsd', parser))
doc = etree.parse('document.xml', parser)
if schema.validate(doc):
print('valid')
else:
for e in schema.error_log:
print(f'{e.line}:{e.column} {e.message}')
# lxml wraps libxml2, so this is XSD 1.0. For xs:assert and xs:alternative use
# the pure-Python implementation instead:
# import xmlschema
# xmlschema.XMLSchema11('schema.xsd').validate('document.xml')import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// With Xerces on the classpath, XSD 1.1 is one string away:
// SchemaFactory.newInstance("http://www.w3.org/XML/XMLSchema/v1.1");
// "file" allows xs:import of a schema next to this one; http is refused.
// Pass "" instead to block every external reference, local ones included.
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
Schema schema = factory.newSchema(new StreamSource(new java.io.File("schema.xsd")));
Validator validator = schema.newValidator();
validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
// Without an ErrorHandler the first error throws and you see one problem.
// With one, Xerces carries on and reports the lot.
validator.setErrorHandler(new DefaultHandler() {
@Override public void error(SAXParseException e) {
System.out.printf("%d:%d %s%n", e.getLineNumber(), e.getColumnNumber(), e.getMessage());
}
@Override public void fatalError(SAXParseException e) throws SAXParseException {
throw e;
}
});
validator.validate(new StreamSource(new java.io.File("document.xml")));using System;
using System.Xml;
using System.Xml.Schema;
var schemaSettings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null, // no xs:import over the network
};
var schemas = new XmlSchemaSet { XmlResolver = null };
using (var schemaReader = XmlReader.Create("schema.xsd", schemaSettings))
{
schemas.Add(null, schemaReader); // null: take targetNamespace from the file
}
var settings = new XmlReaderSettings
{
ValidationType = ValidationType.Schema,
Schemas = schemas,
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
};
// Never follow xsi:schemaLocation from the instance document.
settings.ValidationFlags &= ~XmlSchemaValidationFlags.ProcessSchemaLocation;
// Without a handler the first error throws; with one, validation continues.
settings.ValidationEventHandler += (_, e) =>
Console.WriteLine($"{e.Exception.LineNumber}:{e.Exception.LinePosition} {e.Message}");
using var reader = XmlReader.Create("document.xml", settings);
while (reader.Read()) { } // validation happens as the document is read
// System.Xml.Schema is XSD 1.0, and Microsoft has said it is not moving past it.<?php
// DOMDocument is libxml2, so this is the same engine and the same XSD 1.0.
libxml_use_internal_errors(true);
$doc = new DOMDocument();
if (!$doc->loadXML($source, LIBXML_NONET)) {
fwrite(STDERR, "The document is not well-formed.\n");
exit(1);
}
// schemaValidateSource() takes the XSD as a string if you already have it.
if (!$doc->schemaValidate('schema.xsd')) {
foreach (libxml_get_errors() as $e) {
fprintf(STDERR, "%d:%d %s\n", $e->line, $e->column, trim($e->message));
}
libxml_clear_errors();
exit(1);
}
echo "valid\n";# xmllint ships with libxml2 and is the same engine as this page.
# --nonet stops it fetching anything the document or the schema references.
xmllint --noout --nonet --schema schema.xsd document.xml
# Exit status is 0 when the document is valid, non-zero otherwise, and the
# diagnostics go to stderr in file:line: form.
# A schema split across xs:import files works here, because xmllint reads the
# sibling files from disk. That is the one thing a browser cannot do.
# No widely installed command-line tool does XSD 1.1; for that you need
# Xerces-J or Saxon-EE, driven from code.Note the handler pattern in the Java and C# samples: both APIs throw on the first error unless you install one, which is why so much production code reports a single problem at a time.
Common questions
Are my XML and my XSD uploaded anywhere?
No. Both panes stay in this tab. libxml2 runs as WebAssembly in a Web Worker here, there is no upload endpoint, and the build has no network path of its own.
That matters more here than on most pages: a schema names every field, every code list and every internal boundary, and is often under NDA when the document is only test data. Open your network panel, press Validate, and watch the engine load once and then nothing further. Both panes are kept in localStorage so a refresh does not lose your work, and anything over 300 KB is not stored at all.
Does it support XSD 1.1, or xs:assert?
No. libxml2 is XSD 1.0, so xs:assert, xs:alternative, xs:openContent and xs:override will not compile. Instead of a puzzling "element assert is not expected here", the panel says the schema is well-formed XML but not a usable XSD and names 1.1 as the likely cause.
For 1.1, use Xerces-J, which is free and gives you a 1.1 SchemaFactory by changing one namespace string, Saxon-EE, or the Python xmlschema package. Nothing browser-based does it: they are all libxml2 builds.
My schema uses xs:import. Can I still validate here?
Only if you flatten it first. Resolving an xs:import means reading a file or making a request, and this build registers no resource loader, the same decision that stops it fetching external entities, so the schema will not compile.
Either paste the imported type definitions into the document you validate against, or run xmllint locally, where the sibling .xsd files are read from disk. Most large public schema families, UBL and FpML among them, need that treatment.
What does cvc-complex-type.2.4.a mean?
An element turned up where the schema did not allow it. The message ends with the names that would have been legal there, in braces, so "Invalid content was found starting with element 'item'. One of '{qty}' is expected." means qty was due next.
Three causes cover nearly all of it: the children are in the wrong order for an xs:sequence, by far the most common; the element is not declared at all, usually a typo or a field one side of an integration added; or the name is right and the namespace is wrong, which the message cannot show you because it prints the local name.
Will it fetch the schema named in xsi:schemaLocation?
Never. The specification calls that attribute a hint and lets the invoking application direct the processor not to dereference it, so declining is conformant rather than a shortcut.
The reasons go beyond privacy: following an attacker-controlled URL out of an untrusted document is a request forgery primitive, and it ties your result to whatever a third party is serving today.
How large a document can it check?
Twenty megabytes. Loading a larger file is refused, because everything is held in this page's memory and validation builds a full tree plus the compiled schema.
For genuinely large documents, run xmllint locally: the same libxml2 code, without the browser ceiling.