Please check the errata for any errors or issues reported since publication.
See also translations.
This document is also available in these non-normative formats: XML.
Copyright © 2000 W3C® (MIT, ERCIM, Keio, Beihang). W3C liability, trademark and document use rules apply.
XPath 4.0 is an expression language that allows the processing of values conforming to the data model defined in [XQuery and XPath Data Model (XDM) 4.0]. The name of the language derives from its most distinctive feature, the path expression, which provides a means of hierarchic addressing of the nodes in an XML tree. As well as modeling the tree structure of XML, the data model also includes atomic items, function items, maps, arrays, and sequences. This version of XPath supports JSON as well as XML, and adds many new functions in [XQuery and XPath Functions and Operators 4.0].
XPath 4.0 is a superset of XPath 3.1. A detailed list of changes made since XPath 3.1 can be found in I Change Log.
This is a draft prepared by the QT4CG (officially registered in W3C as the XSLT Extensions Community Group). Comments are invited.
The publications of this community group are dedicated to our co-chair, Michael Sperberg-McQueen (1954–2024).
Michael was central to the development of XML and many related technologies. He brought a polymathic breadth of knowledge and experience to everything he did. This, combined with his indefatigable curiosity and appetite for learning, made him an invaluable contributor to our project, along with many others. We have lost a brilliant thinker, a patient teacher, and a loyal friend.
This section discusses each of the basic kinds of expression. Each kind of expression has a name such as PathExpr, which is introduced on the left side of the grammar production that defines the expression. Since XPath 4.0 is a composable language, each kind of expression is defined in terms of other expressions whose operators have a higher precedence. In this way, the precedence of operators is represented explicitly in the grammar.
The order in which expressions are discussed in this document does not reflect the order of operator precedence. In general, this document introduces the simplest kinds of expressions first, followed by more complex expressions. For the complete grammar, see Appendix [A XPath 4.0 Grammar].
The highest-level symbol in the XPath grammar is XPath.
XPath | ::= | Expr |
Expr | ::= | (ExprSingle ++ ",") |
ExprSingle | ::= | ForExpr |
ExprSingle | ::= | ForExpr |
ForExpr | ::= | ForClauseForLetReturn |
LetExpr | ::= | LetClauseForLetReturn |
QuantifiedExpr | ::= | ("some" | "every") (QuantifierBinding ++ ",") "satisfies" ExprSingle |
IfExpr | ::= | "if" "(" Expr ")" (UnbracedActions | BracedAction) |
OrExpr | ::= | AndExpr ("or" AndExpr)* |
The XPath 4.0 operator that has lowest precedence is the comma operator, which is used to combine two operands to form a sequence. As shown in the grammar, a general expression (Expr) can consist of multiple ExprSingle operands, separated by commas.
The name ExprSingle denotes an expression that does not contain a top-level comma operator (despite its name, an ExprSingle may evaluate to a sequence containing more than one item.)
The symbol ExprSingle is used in various places in the grammar where an expression is not allowed to contain a top-level comma. For example, each of the arguments of a function call must be a ExprSingle, because commas are used to separate the arguments of a function call.
After the comma, the expressions that have next lowest precedence are ForExpr, LetExpr, QuantifiedExpr, IfExpr, and OrExpr. Each of these expressions is described in a separate section of this document.
XPath provides two closely-related expressions, called For and Let expressions, that can be used to bind variables to values. These are described in the following sections.
Multiple for and let clauses can be combined in an expression without an intervening return keyword. [Issue 22 PR 28 18 December 2020]
The type of a variable used in a let expression can be declared. [Issue 796 PR 1131 1 April 2024]
Sequences, arrays, and maps can be destructured in a let expression to extract their components into multiple variables. [Issue 37 PR 2055 17 June 2025]
XPath allows a variable to be declared and bound to a value using a let expression.
LetExpr | ::= | LetClauseForLetReturn |
LetClause | ::= | "let" (LetBinding ++ ",") |
LetBinding | ::= | LetValueBinding | LetSequenceBinding | LetArrayBinding | LetMapBinding |
LetValueBinding | ::= | VarNameAndType ":=" ExprSingle |
VarNameAndType | ::= | "$" EQNameTypeDeclaration? |
EQName | ::= | QName | URIQualifiedName |
TypeDeclaration | ::= | "as" SequenceType |
ExprSingle | ::= | ForExpr |
LetSequenceBinding | ::= | "$" "(" (VarNameAndType ++ ",") ")" TypeDeclaration? ":=" ExprSingle |
SequenceType | ::= | ("empty-sequence" "(" ")") |
LetArrayBinding | ::= | "$" "[" (VarNameAndType ++ ",") "]" TypeDeclaration? ":=" ExprSingle |
LetMapBinding | ::= | "$" "{" (VarNameAndType ++ ",") "}" TypeDeclaration? ":=" ExprSingle |
ForLetReturn | ::= | ForExpr | LetExpr | ("return" ExprSingle) |
ForExpr | ::= | ForClauseForLetReturn |
A let expression is evaluated as follows:
If the let expression uses multiple variables, it is first expanded to a set of nested let expressions, each of which uses only one variable. Specifically, any separating comma is replaced by let.
In a LetValueBinding such as let $V as T := EXPR:
The variable V is called the range variable.
The sequence type T is called the declared type. If there is no declared type, then item()* is assumed.
The expression EXPR is evaluated, and its value is converted to the declared type by applying the coercion rules. The resulting value is called the binding sequence.
In a LetSequenceBinding such as let $( $A1 as T1, $A2 as T2, ... , $An as Tn ) as ST := EXPR:
The sequence type ST is called the declared sequence type. If there is no declared sequence type, then item()* is assumed.
The expression EXPR is evaluated, and its value is converted to the declared sequence type ST by applying the coercion rules. Call the resulting (coerced) value V.
Each variable Ai (for i in 1 to n-1) is effectively replaced by a LetValueBinding of the form let Ai as Ti := items-at(V, i). That is, a range variable named Ai is declared, whose binding sequence is the item V[ i ], after coercion to the type Ti if specified. If Ti is absent, no further coercion takes place (the default is effectively item()?).
Note:
If i exceeds the length of the sequence V, then Ai is bound to an empty sequence. This will cause a type error if type Ti does not permit an empty sequence.
Note:
It is permissible to bind several variables with the same name; all but the last are occluded. A useful convention is therefore to bind items in the sequence that are of no interest to the variable $_: for example let $( $_, $_, $x ) := EXPR effectively binds $x to the third item in the sequence and causes the first two items to be ignored.
The expression:
let $( $a, $b as xs:integer, $local:c ) := (2, 4, 6)
return $a + $b + $local:cis expanded to:
let $temp := (2, 4, 6)
let $a := fn:items-at($temp, 1)
let $b as xs:integer := fn:items-at($temp, 2)
let $local:c := fn:items-at($temp, 3)
return $a + $b + $local:cwhere $temp is some variable name that is otherwise unused.
Consider the element $E := <e A="p q r" B="x y z"/>, where $E has been validated against a schema that defines both attributes A and B as being lists of strings.
Then the expression:
let $( $a as xs:string*, $b as xs:string* ) := $E/(@A, @B)binds $a to the sequence ("p", "q", "r") and $b to the sequence ("x", "y", "z"). The evaluation of the expression $E/(@A, @B) returns a sequence of two attribute nodes; the value of $a is formed by atomizing the first of these nodes, while $b is formed by atomizing the second.
The last variable An is effectively replaced by a LetValueBinding of the form let An as Tn := subsequence(V, n). That is, the last variable is bound to the rest of the binding sequence (or to the empty sequence if the binding sequence has fewer items than the number of variables).
Note:
If i exceeds the length of the sequence V, then Ai is bound to an empty sequence. This will cause a type error if type Ti does not permit an empty sequence.
Note:
It is permissible to bind several variables with the same name; all but the last are occluded. A useful convention is therefore to bind items in the sequence that are of no interest to the variable $_: for example let $( $_, $_, $x ) := EXPR effectively binds $x to the third item in the sequence and causes the first two items to be ignored.
The expression:
let $( $a, $b as xs:integer, $local:c ) := (2, 4, 6)
return $a + $b + $local:cis expanded to:
let $temp := (2, 4, 6)
let $a := fn:items-at($temp, 1)
let $b as xs:integer := fn:items-at($temp, 2)
let $local:c := fn:subsequence($temp, 3)
return $a + $b + $local:cwhere $temp is some variable name that is otherwise unused.
Consider the element $E := <e A="p q r" B="x y z"/>, where $E has been validated against a schema that defines both attributes A and B as being lists of strings.
Then the expression:
let $( $a as xs:string*, $b as xs:string* ) := $E/(@A, @B)binds $a to the sequence ("p", "q", "r") and $b to the sequence ("x", "y", "z"). The evaluation of the expression $E/(@A, @B) returns a sequence of two attribute nodes; the value of $a is formed by atomizing the first of these nodes, while $b is formed by atomizing the second.
In a LetArrayBinding such as let $[ $A1 as T1, $A2 as T2, ... , $An as Tn ] as AT := EXPR:
The sequence type AT is called the declared array type. If there is no declared array type, then array(*) is assumed.
The expression EXPR is evaluated, and its value is converted to the declared array type AT by applying the coercion rules. A type error [err:XPTY0004] is raised if the result is not a singleton array. Call the resulting (coerced) value V.
Each variable Ai (for i in 1 to n) is effectively replaced by a LetValueBinding of the form let Ai as Ti := array:get(V, i, ()). That is, a range variable named Ai is declared, whose binding sequence is the array member V ? i, after coercion to the type Ti if specified. If Ti is absent, no further coercion takes place (the default is effectively item()*).
Note:
If i exceeds the length of the array V, then Ai is bound to an empty sequence. This will cause a type error if type Ti does not permit an empty sequence.
If i exceeds the length of the array V, then a dynamic error [err:FOAR0001]FO is raised.
Note:
It is permissible to bind several variables with the same name; all but the last are occluded. A useful convention is therefore to bind items in the sequence that are of no interest to the variable $_: for example let $( $_, $_, $x ) := EXPR effectively binds $x to the third item in the sequence and causes the first two items to be ignored.
The expression:
let $[ $a, $b as xs:integer, $local:c ] := [ 2, 4, 6 ] return $a + $b + $local:c
is expanded to:
let $temp := [ 2, 4, 6 ] let $a := array:get($temp, 1, ()) let $b as xs:integer := array:get($temp, 2, ()) let $local:c := array:get($temp, 3, ()) return $a + $b + $local:c
where $temp is some variable name that is otherwise unused.
In a LetMapBinding such as let ${ $A1 as T1, $A2 as T2, ... , $An as Tn } as MT := EXPR:
The sequence type MT is called the declared map type. If there is no declared map type, then map(*) is assumed.
The expression EXPR is evaluated, and its value is converted to the declared map type MT by applying the coercion rules. A type error [err:XPTY0004] is raised if the result is not a singleton map. Call the resulting (coerced) value V.
Each variable Ai (for i in 1 to n) is effectively replaced by a LetValueBinding of the form let Ai as Ti := map:get(V, "Ni", ()), where Ni is the local part of the name of the variable Ai. That is, a range variable named Ai is declared, whose binding sequence is the value of the map entry in V whose key is an xs:string (or xs:anyURI or xs:untypedAtomic) equal to the local part of the variable name, after coercion to the type Ti if specified. If Ti is absent, no further coercion takes place (the default is effectively item()*).
Note:
If there is no entry in the map with a key corresponding to the variable name, then the variable Ai is bound to an empty sequence. This will cause a type error if type Ti does not permit an empty sequence.
Note:
It is not possible to use this mechanism to bind variables to values in a map unless the keys in the map are strings in the form of NCNames.
The expression:
let ${ $a, $b as xs:integer, $local:c } := { "a": 2, "b": 4, "c": 6, "d": 8 }
return $a + $b + $local:cis expanded to:
let $temp := { "a": 2, "b": 4, "c": 6 }
let $a := map:get($temp, "a", ())
let $b as xs:integer := map:get($temp, "b", ())
let $local:c := map:get($temp, "c", ())
return $a + $b + $local:cwhere $temp is some variable name that is otherwise unused.
The expression in the ForLetReturn part (that is, the following LetExpr or ForExpr, or the ExprSingle that follows the return keyword) is called the return expression. The result of the let expression is obtained by evaluating the return expression with a dynamic context in which each range variable is bound to the corresponding binding sequence.
The scope of a variable bound in a let expression is the return expression. The scope does not include the expression to which the variable is bound. The following example illustrates how a variable binding may reference another variable bound earlier in the same let expression:
let $x := doc('a.xml')/*, $y := $x//*
return $y[@value gt $x/@min]Note:
It is not required that the variables should have distinct names. It is permitted, for example, to write:
let $x := "[A fine romance]" let $x := substring-after($x, "[") let $x := substring-before($x, "]") return upper-case($x)
which returns the result "A FINE ROMANCE". Note that this expression declares three separate variables which happen to have the same name; it should not be read as declaring a single variable and binding it successively to different values.