The following associated resources are available: Specification in XML format, XSD 1.1 Schema for XSLT 4.0 Stylesheets (non-normative), Relax-NG Schema for XSLT 4.0 Stylesheets (non-normative), Stylesheet for XML-to-JSON conversion (non-normative)
Copyright © 2026 W3C® (MIT, ERCIM, Keio, Beihang). W3C liability, trademark and document use rules apply.
This specification defines the syntax and semantics of XSLT 4.0, a language designed primarily for transforming XML documents into other XML documents.
XSLT 4.0 is a revised version of the XSLT 3.0 Recommendation [XSLT 3.0] published on 8 June 2017. Changes are presented in 1.2 What’s New in XSLT 4.0?.
XSLT 4.0 is designed to be used in conjunction with XPath 4.0, which is defined in [XPath 4.0]. XSLT shares the same data model as XPath 4.0, which is defined in [XDM 3.0], and it uses the library of functions and operators defined in [Functions and Operators 4.0]. XPath 4.0 and the underlying function library introduce a number of enhancements, for example the availability of union and record types.
This document contains hyperlinks to specific sections or definitions within other documents in this family of specifications. These links are indicated visually by a superscript identifying the target specification: for example XP for XPath 4.0, DM for the XDM data model version 4.0, FO for Functions and Operators version 4.0.
This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.
This document has no official standing. It is produced by the editor as a proposal for community review. Insofar as it copies large amounts of text from the W3C XSLT 3.0 Recommendation, W3C copyright and similar provisions apply.
The publications of this community group are dedicated to our co-chair, Michael Sperberg-McQueen (1954–2024).
XSLT 4.0 introduces two new and closely-related constructs allowing item types to be given names, and to be referred to by name anywhere that item types are used, for example in function declarations and variable declarations.
The xsl:item-type declaration allows any item type to be given a name. It is particularly useful to avoid repetitive use of the same choice types, enumeration types, or function types, and means that if the definition changes, the change only needs to be made in one place.
The xsl:record-type declaration takes this a step further. Like xsl:item-type, it allows a name to be given to any record type. In addition, though, it offers two further capabilities:
Named record types can be recursive, allowing definitions of recursive data structures such as lists and trees.
Declaring a named record type automatically establishes a constructor function for records of that type; the constructor function has the same name as the record type itself.
<!-- Category: declaration -->
<xsl:record-type
name = eqname
extensible? = boolean〔'no'〕
visibility? = "private" | "public"〔'private'〕 >
<!-- Content: (xsl:field*) -->
</xsl:record-type>
<xsl:field
name = ncname
as? = sequence-type〔'item()*'〕
required? = boolean〔'yes'〕
default? = expression />
An xsl:record-type declaration associates a name with a record type, and allows the record type to be referenced by name throughout the stylesheet package.
The following example declares a named record type for complex numbers, and uses it in a variable declaration and a function declaration.
<xsl:record-type name="cx:complex"> <xsl:field name="r" as="xs:double"/> <xsl:field name="i" as="xs:double"/> </xsl:record-type> <xsl:variable name="i" as="cx:complex" select="cx:complex(0, 1)"/> <xsl:function name="cx:add" as="cx:complex"> <xsl:param name="x" as="cx:complex"/> <xsl:param name="y" as="cx:complex"/> <xsl:sequence select="cx:complex($x?r + $y?r, $x?i + $y?i)"/> </xsl:function>
Note how the record type declaration has implicitly declared a constructor function cx:complex that can be used to create instances of the item type.
[ERR XTSE4050] It is a static error if the names of the fields in an xsl:record-type declaration are not distinct.
[ERR XTSE4051] It is a static error if an xsl:field element has a default attribute unless it specifies required="no".
An xsl:record-type declaration has two effects:
In the same way as xsl:item-type, it defines a named item type in the static context, allowing the record type to be referenced by name anywhere an ItemType can appear, for example in the declarations of functions and variables. Unlike types declared in xsl:item-type, however, named record types can be recursive.
An xsl:record-type declaration also implicitly defines a constructor function, of the same name, and adds this to the set of statically known function definitionsXP in the static context.
Because of its dual role, the name of an xsl:record-type declaration must be valid both as an item type name and as a function name. This means the name must be distinct from other type names and function names in the static context. It also means that the name must be in a namespace.
Considered as an item type, the xsl:record-type declaration is equivalent to an xsl:item-type declaration formed by the following template rule, evaluated in the context of a namespace alias <xsl:namespace-alias stylesheet-prefix="t" result-prefix="xsl"/>:
<xsl:template match="xsl:record-type" expand-text="yes"> <t:item-type name="{@name}" visibility="{@visibility otherwise 'private'}"> <xsl:attribute name="as"> <xsl:text>record(</xsl:text> <xsl:for-each select="xsl:field" separator=", "> <xsl:variable name="optional" as="xs:boolean" select="normalize-space(@required) = ('no','false','0')"/> {@name}{'?'[$optional]} as {@as otherwise 'item()*'} </xsl:for-each> <xsl:if test="normalize-space(@extensible) = ('yes','true','1')"> <xsl:text>, *</xsl:text> </xsl:if> <xsl:text>)</xsl:text> </xsl:attribute> </t:item-type> </xsl:template>
This generated xsl:item-type declaration must meet all the constraints placed on user-written xsl:item-type declarations, including the rule requiring names to be unique, but not including the rule disallowing recursive references.
For example, the declaration:
<xsl:record-type name="cx:complex" extensible="yes"> <xsl:field name="r" as="xs:double"/> <xsl:field name="i" as="xs:double" required="no" default="0"/> </xsl:record-type>
produces the equivalent item type declaration:
<xsl:item-type name="cx:complex" as="record(r as xs:double, i? as xs:double, *)"/>
Considered as a function declaration, an xsl:record-type declaration is equivalent to an xsl:function declaration formed by the following template rule, evaluated in the context of a namespace alias <xsl:namespace-alias stylesheet-prefix="t" result-prefix="xsl"/>:
<xsl:template match="xsl:record-type"> <t:function name="{@name}" as="{@name}" visibility="{(@visibility otherwise 'private')}"> <!-- if the record type is extensible, choose a name for the options parameter --> <xsl:variable name="options-param" as="xs:string?"> <xsl:if test="normalize-space(@extensible) = ('yes', 'true', 'a')" then="('options', (1 to count(xsl:field)) ! `options{.}`) [not(. = current()/xsl:field/@name)][1]"/> </xsl:variable> <!-- declare the parameters --> <xsl:for-each select="xsl:field"> <t:param name="{@name}" required="{@required otherwise 'yes'}"> <xsl:attribute name="as"> <xsl:variable name="as" select="normalize-space(@as otherwise 'item()*')"/> <xsl:variable name="optional" select="normalize-space(@required) = ('no', 'false', '0')"/> <xsl:choose> <xsl:when test="not($optional)" select="$as"/> <!-- for optional fields, amend the required type to allow () --> <xsl:when test="matches($as, '[?*]$')" select="$as"/> <xsl:when test="matches($as, '\+$')" select="replace($as, '\+$', '*')"/> <xsl:otherwise select="$as || '?'"/> </xsl:choose> </xsl:attribute> <xsl:if test="@default"> <xsl:attribute name="select" select="@default"/> </xsl:if> </t:param> </xsl:for-each> <!-- for an extensible record type, declare the options parameter --> <xsl:if test="exists($options-param)"> <t:param name="{$options-param}" as="map(*)" required="no" select="{}"/> </xsl:if> <!-- function body: construct a map --> <t:map duplicates="fn($first, $second){{$first}}"> <xsl:for-each select="xsl:field"> <xsl:variable name="optional" select="normalize-space(@required) = ('no', 'false', '0')"/> <xsl:choose> <xsl:when test="$optional and not(@default)"> <!-- omit map entries for optional fields if no value is supplied --> <t:if test="exists(${@name})"> <t:map-entry key="'{@name}'" select="${@name}"/> </t:if> </xsl:when> <xsl:otherwise> <t:map-entry key="'{@name}'" select="${@name}"/> </xsl:otherwise> </xsl:choose> </xsl:for-each> <!-- if the record type is extensible, add entries from the options parameter --> <xsl:if test="exists($options-param)"> <t:sequence select="${$options-param}"/> </xsl:if> </t:map> </t:function> </xsl:template>
For example, the declaration:
<xsl:record-type name="cx:complex"> <xsl:field name="r" as="xs:double"/> <xsl:field name="i" as="xs:double" required="no" default="0"/> </xsl:record-type>
produces the equivalent function declaration:
<xsl:function name="cx:complex" as="cx:complex" visibility="private"> <xsl:param name="r" as="xs:double"/> <xsl:param name="i" as="xs:double" required="no" select="0"/> <xsl:map> <xsl:map-entry key="'r'" select="$r"/> <xsl:map-entry key="'i'" select="$i"/> </xsl:map> </xsl:function>
No entry is generated in the constructed map for a field that is declared as optional with no default. So the declaration:
<xsl:record-type name="cx:complex" visibility="public"> <xsl:field name="r" as="xs:double"/> <xsl:field name="i" as="xs:double" required="no"/> </xsl:record-type>
produces the equivalent function declaration:
<xsl:function name="cx:complex" as="cx:complex" visibility="public"> <xsl:param name="r" as="xs:double"/> <xsl:param name="i" as="xs:double?" required="no"/> <xsl:map> <xsl:map-entry key="'r'" select="$r"/> <xsl:if test="exists($i)"> <xsl:map-entry key="'i'" select="$i"/> </xsl:if> </xsl:map> </xsl:function>
If the record type is extensible, the generated function includes an optional options parameter So the declaration:
<xsl:record-type name="cx:complex" extensible="yes"> <xsl:field name="r" as="xs:double"/> <xsl:field name="i" as="xs:double"/> </xsl:record-type>
produces the equivalent function declaration:
<xsl:function name="cx:complex" as="cx:complex" visibility="private"> <xsl:param name="r" as="xs:double"/> <xsl:param name="i" as="xs:double"/> <xsl:param name="options" as="map(*)" required="no" default="{}"/> <xsl:map duplicates="fn($first, $second){$first}"> <xsl:map-entry key="'r'" select="$r"/> <xsl:map-entry key="'i'" select="$i"/> <xsl:sequence select="$options"/> </xsl:map> </xsl:function>
This generated xsl:function declaration must meet all the constraints placed on user-written xsl:function declarations, including the rule requiring the combination of name and arity to be unique.
The generated xsl:function declaration has the import precedence associated with the stylesheet module in which the xsl:record-type declaration appears, and it may be overridden by another xsl:function declaration with higher import precedence. If the visibility is public then it can also be overridden using xsl:override in another package.
The scope of a named record type is the package in which it is declared. If it is declared with visibility="public" then it also becomes available for use in using packages.
The name of the record type is the expanded name formed by resolving the name attribute. Because function names are always in a namespace, the name must be prefixed.
If two xsl:record-type declarations in a package have the same name, then the one with higher import precedence is used.
A record type declaration may refer directly or indirectly to itself if it satisfies the conditions defined in Section 3.2.8.3.1 Recursive Record TypesXP. This allows types to be declared that match recursive data structures such as linked lists and trees.
A named record type with visibility private may be used in the definition of a component (such as a variable, a function, a template, or another named record type) that is itself public. Another package may reference such a component even though it cannot reference the types used in its definition. The fact that the record type is private, however, means that it is impossible to override a variable or function that references the record type.
This example illustrates the definition of a recursive record type. The record type represents a node of a binary tree containing a payload value in each node, together with optional references to left and right subtrees. As well as the data fields, the record type defines a depth function that returns the maximum depth of the tree, by making recursive calls on its subtrees.
<xsl:record-type name="my:binary-tree"> <xsl:field name="left" as="my:binary-tree?"/> <xsl:field name="payload" as="item()*"/> <xsl:field name="right" as="my:binary-tree?"/> <xsl:field name="depth" as="fn(my:binary-tree) as xs:integer" default="fn() {1 + max((?left =?> depth(), ?right =?> depth())) otherwise 0)}"/> </xsl:record-type>
The %method=?> operator is described in [TITLE OF XP40 SPEC, TITLE OF methodslookup-arrow-expression SECTION]XP40. Its effect is that the expressionto make a $M?depth()dynamic evaluates thecall on a function item left?depththat with the mapis present as an entry in a map, $Mpassing that map implicitly as the context itemfirst argument. It thus mimics method invocation in object-oriented languages, though there is no inheritance or encapsulation.
The following code builds a simple tree and calculates its depth:
let $tree := my:binary-tree(
my:binary-tree((), 17, ()),
18,
my:binary-tree((), 19,
my:binary-tree((), 20, ())))
return $tree?depth()let $tree := my:binary-tree(
my:binary-tree((), 17, ()),
18,
my:binary-tree((), 19,
my:binary-tree((), 20, ())))
return $tree =?> depth()Returning the result 3.
Template rules define the processing that can be applied to items that match a particular pattern.
The xsl:apply-imports and xsl:next-match instructions automatically pass supplied parameters to the overridden template rule. [Issue 1861 ]
<!-- Category: instruction -->
<xsl:apply-imports>
<!-- Content: xsl:with-param* -->
</xsl:apply-imports>
<!-- Category: instruction -->
<xsl:next-match>
<!-- Content: (xsl:with-param | xsl:fallback)* -->
</xsl:next-match>
A template rule that is being used to override another template rule (see 6.5 Conflict Resolution for Template Rules) can use the xsl:apply-imports or xsl:next-match instruction to invoke the overridden template rule. The xsl:apply-imports instruction only considers template rules in imported stylesheet modules; the xsl:next-match instruction considers all template rules that have not already been used. Both instructions will invoke the built-in template rule for the context item (see 6.8 Built-in Template Rules) if no other template rule is found.
[Definition: At any point in the processing of a stylesheet, there may be a current template rule. Whenever a template rule is chosen as a result of evaluating xsl:apply-templates, xsl:apply-imports, or xsl:next-match, the template rule becomes the current template rule for the evaluation of the rule’s sequence constructor.]
The current template rule is cleared (becomes absent) by any instruction that evaluates an operand with changed focus. It is therefore cleared when evaluating instructions contained within:
xsl:copy if and only if there is a select attribute
A global xsl:variable or xsl:param
xsl:template if and only if the called template specifies <xsl:context-item use="absent"/>
Note:
The current template rule is not affected by invoking named attribute sets (see 10.2 Named Attribute Sets), or named templates (see 10.1 Named Templates) unless <xsl:context-item use="absent"/> is specified. While evaluating a global variable or the default value of a stylesheet parameter (see 9.5 Global Variables and Parameters) the current template rule is absent.
These rules ensure that when xsl:apply-imports or xsl:next-match is called, the context item is the same as when the current template rule was invoked.
Both xsl:apply-imports and xsl:next-match search for a template rule that matches the context item, and that is applicable to the current mode (see 6.7 Modes). In choosing a template rule, they use the usual criteria such as the priority and import precedence of the template rules, but they consider as candidates only a subset of the template rules in the stylesheet. This subset differs between the two instructions:
The xsl:apply-imports instruction considers as candidates only those template rules contained in stylesheet levels that are descendants in the import tree of the stylesheet level that contains the current template rule.
Note:
This is not the same as saying that the search considers all template rules whose import precedence is lower than that of the current template rule.
[ERR XTSE3460] It is a static error if an xsl:apply-imports element appears in a template rule declared within an xsl:override element. (To invoke the template rule that is being overridden, xsl:next-match should therefore be used.)
The xsl:next-match instruction considers as candidates all those template rules that come after the current template rule in the ordering of template rules implied by the conflict resolution rules given in 6.5 Conflict Resolution for Template Rules.
This process could be implemented by the following algorithm:
Set a flag active to false.
Follow the rules in 6.5 Conflict Resolution for Template Rules to find the best matching rule, without raising any errors or warnings if there are multiple matches.
If the template rule identified is the current template rule, discard this rule, and repeat the process from step 2 with the flag active set to true.
Otherwise, if active is set to false, discard this rule, and repeat the process from step 2, with the flag active still set to false
Otherwise (if active is set to true) use the selected rule.
Note:
An alternative implementation would be to maintain, not just the current template rule, but a list of rules that have been used to process the context item. The implementation of xsl:next-match can then eliminate these rules from the search.
In the absence of type patterns, it is possible to define a total ordering of template rules for each mode, and to exclude those rules that appear before the current template rule in this ordering. The introduction of type patterns makes this approach more challenging, since types are partially ordered.
Note:
Because a template rule declared as a child of xsl:override has higher precedence than any template rule declared in the used package (see 3.5.4 Overriding Template Rules from a Used Package), the effect of xsl:next-match within such a template rule is to consider as candidates first any other template rules for the same mode within the same xsl:use-package element (taking into account explicit and implicit priority, and document order, in the usual way), and then all template rules in the used package.
If no matching template rule is found, both xsl:apply-imports and xsl:next-match cause the built-in template rule for the mode to be invoked.
If multiple matching template rules with the same explicit or implicit priority are found, both xsl:apply-imports and xsl:next-match respect the on-multiple-match and warning-on-multiple-match attributes of the mode declaration.
Note:
If is entirely possible for xsl:apply-templates to identify a template rule unambiguously, and for xsl:apply-imports or xsl:next-match then to fail because there is no unambiguous second-choice template rule.
If a matching template rule R is found, then the result of the xsl:next-match or xsl:apply-imports instruction is the result of invoking R. The parameters that are passed to R are as follows:
All parameters explicitly set using xsl:with-param child elements (see 9.10 Setting Parameter Values).
If the effective version of the xsl:next-match or xsl:apply-imports instruction is 4.0 or greater, then all non-tunnel parameters that were supplied in the invocation of the current template rule, excluding any whose names match the names of parameters appearing in child xsl:with-param elements.
All tunnel parameters as described in 10.1.6 Tunnel Parameters.
Note:
The implicit passing of non-tunnel parameters is new in XSLT 4.0, and happens only if [xsl:]version is set to 4.0 (or greater) on the instruction, or on some ancestor element in the stylesheet. There may be cases where this change introduces a backward incompatibility: specifically, if the invoked template rule declares a default value for an optional parameter, it will now take the implicitly passed value rather than the default value. The 3.0 behavior can be therefore be retained by setting version="3.0" on the xsl:next-match or xsl:apply-imports instruction.
The template rule R is evaluated with the same focus as the xsl:next-match or xsl:apply-imports instruction. The current template rule changes to be R. The current mode does not change.
Note:
In the case where the current template rule T is declared within an xsl:override element in a using package P, while the selected rule R is declared within a different package Q, and where the current mode is MP (mode M in package P), the effect is that the current mode for evaluation of R remains MP rather than reverting to its corresponding mode MQ (mode M in package Q). If R contains an xsl:apply-templates instruction that uses mode="#current", then the set of template rules considered by this instruction will therefore include any overriding template rules declared in P as well as the original rules declared in Q.
If no matching template rule is found that satisfies these criteria, the built-in template rule for the context item is used (see 6.8 Built-in Template Rules).
[ERR XTDE0560] It is a dynamic error if xsl:apply-imports or xsl:next-match is evaluated when the current template rule is absent.
For example, suppose the stylesheet doc.xsl contains a template rule for example elements:
<xsl:template match="example"> <pre><xsl:apply-templates/></pre> </xsl:template>
Another stylesheet could import doc.xsl and modify the treatment of example elements as follows:
<xsl:import href="doc.xsl"/> <xsl:template match="example"> <div style="border: solid red"> <xsl:apply-imports/> </div> </xsl:template>
The combined effect would be to transform an example into an element of the form:
<div style="border: solid red"><pre>...</pre></div>
An xsl:fallback instruction appearing as a child of an xsl:next-match instruction is ignored by an XSLT 2.0 or 3.0, 3.0, or 4.0 processor, but can be used to define fallback behavior when the stylesheet is processed by an XSLT 1.0 processor with forwards compatible behavior.
This example shows how an input element such as:
<phrase upper-case="true" italic="true" bold="false" underscore="true">Hannover</phrase>
might be transformed into:
<italic><underscore>HANNOVER</underscore></italic>
The following template rules achieve the required effect:
<xsl:template match="phrase" priority="10"> <xsl:next-match> <xsl:with-param name="upper-case" select="xs:boolean(@upper-case)"/> </xsl:next-match> </xsl:template> <xsl:template match="phrase[xs:boolean(@italic)]" priority="8"> <italic> <xsl:next-match/> </italic> </xsl:template> <xsl:template match="phrase[xs:boolean(@bold)]" priority="6"> <bold> <xsl:next-match/> </bold> </xsl:template> <xsl:template match="phrase[xs:boolean(@underscore)]" priority="4"> <underscore> <xsl:next-match/> </underscore> </xsl:template> <xsl:template match="phrase" priority="2"> <xsl:param name="upper-case" as="xs:boolean?"/> <xsl:if test="$upper-case" then="upper-case(.)" else="string(.)"/> </xsl:template>
Note how the $upper-case parameter is passed implicitly through the chain of template rules.