XSLT versions, engines and where transformations run

XSLT 1.0 is universal and XSLT 3.0 is far more capable, but the version you can use is decided by the engine you deploy.

The three versions in practice

VersionYearKey additionsTypical engine
1.01999Templates, XPath 1.0, basic output controllibxslt, Xalan, browser engines, .NET
2.02007Sequences, types, grouping, regex, user functionsSaxon 9, Xalan-J 2.7 (partial)
3.02017Maps, arrays, streaming, packages, higher-order functionsSaxon 9.7+, SaxonJS
  • Everything published as a browser transform is 1.0, because no browser shipped a 2.0 engine.
  • libxslt is the engine behind xsltproc, PHP's xsl extension and many Python bindings. It is 1.0 plus a handful of extension functions.
  • .NET's XslCompiledTransform is 1.0; .NET does not ship a 2.0 or 3.0 processor.
  • Java's JAXP defaults to Xalan 1.0 but can be pointed at Saxon by setting a system property, which is the usual route to 2.0 in a JVM.
  • If you need grouping, regex or date arithmetic, choose the engine before writing a single template.
# xsltproc (libxslt) -- XSLT 1.0, installed on most Linux systems
xsltproc --output out.html transform.xsl input.xml

# Saxon-HE on the command line -- XSLT 3.0, free edition
java -cp saxon-he-12.5.jar net.sf.saxon.Transform -s:input.xml -xsl:transform.xsl -o:out.html

# Passing a parameter from the shell
xsltproc --stringparam locale en-GB transform.xsl input.xml

Where the transform should run

PlacementUse it whenWatch out for
Build timeStatic site generation, report templatesFailure stops the build, which is what you want
Server sideInput arrives per request, output must be freshPer-request engine warm-up cost
Client sideLegacy intranet pages, XML data islandsOnly 1.0, slow, and blocked in some browsers
Inside the databaseVery large documents that must not leave the serverVendor-specific dialects and painful debugging
from lxml import etree

# Server-side transform, 1.0 via libxslt, compiled once and reused
transform = etree.XSLT(etree.parse("transform.xsl"))
result = transform(etree.parse("input.xml"), locale="en-GB")
print(str(result))
⚠️
A browser transform means the XML and the stylesheet are both fetched by the client, so anything in them is public. Never put credentials, internal URLs or unpublished data in a document you intend to transform in the browser.

FAQ

Can I write one stylesheet that runs on both 1.0 and 3.0 engines?
Yes, by marking the stylesheet version="1.0" and avoiding 2.0 features. Saxon always runs a 1.0 stylesheet in backwards-compatible mode, so the semantics stay 1.0.
Is XSLT still worth learning?
For XML-to-XML and XML-to-office-document work it remains the shortest path, and XSLT 3.0 is genuinely expressive. For HTML from a database, a template language in your application is usually easier to maintain.

XSLT 2.0 and 3.0 beyond version 1.0 Calling XSLT from Java, .NET and Python

Last refreshed 2026-09-18.