dgtlmoon/changedetection.io · warning · ValidationError
'%(expression)s' is not a valid XPath expression. (%(error)s
Error message
'%(expression)s' is not a valid XPath expression. (%(error)s)
What it means
When XPath is allowed and the line (after stripping 'xpath:') is tested, the validator builds an empty HTML tree and runs elementpath.select(tree, expr, parser=SafeXPath3Parser) under an lxml lock. An elementpath.ElementPathError means the expression is syntactically invalid XPath; the original expression and parser error string are interpolated into the ValidationError message.
Source
Thrown at changedetectionio/forms.py:709
return
# Does it look like XPath?
if line.strip()[0] == '/' or line.strip().startswith('xpath:'):
if not self.allow_xpath:
raise ValidationError("XPath not permitted in this field!")
from lxml import etree, html
import elementpath
from changedetectionio.html_tools import SafeXPath3Parser, lxml_guard, lxml_html_parser
line = line.replace('xpath:', '')
try:
# Runs on a Flask request thread - must share the worker's lxml lock.
with lxml_guard():
tree = html.fromstring("<html></html>", parser=lxml_html_parser())
elementpath.select(tree, line.strip(), parser=SafeXPath3Parser)
except elementpath.ElementPathError as e:
message = field.gettext('\'%(expression)s\' is not a valid XPath expression. (%(error)s)')
raise ValidationError(message % {'expression': line, 'error': str(e)})
except:
raise ValidationError("A system-error occurred when validating your XPath expression")
if line.strip().startswith('xpath1:'):
if not self.allow_xpath:
raise ValidationError("XPath not permitted in this field!")
from lxml import etree, html
from changedetectionio.html_tools import lxml_guard, lxml_html_parser
line = re.sub(r'^xpath1:', '', line)
try:
# Runs on a Flask request thread - must share the worker's lxml lock.
with lxml_guard():
tree = html.fromstring("<html></html>", parser=lxml_html_parser())
tree.xpath(line.strip())
except etree.XPathEvalError as e:
message = field.gettext('\'%(expression)s\' is not a valid XPath expression. (%(error)s)')
raise ValidationError(message % {'expression': line, 'error': str(e)})View on GitHub (pinned to 5d9c7c6da7)
Solutions
- Test the expression in a browser console or an XPath tester with XPath 3.0 semantics
- Fix the reported syntax error (balance [] () "" and correct axis/step names)
- Prefer simple axes like //tag[@attr='value'] which parse under any version
Example fix
# before xpath://div[[text()='hi'] # after xpath://div[text()='hi']
Defensive patterns
Strategy: validation
Validate before calling
from lxml import html
import elementpath
from changedetectionio.html_tools import SafeXPath3Parser
def xpath_ok(expr: str) -> tuple[bool, str]:
try:
tree = html.fromstring('<html></html>')
elementpath.select(tree, expr, parser=SafeXPath3Parser)
return True, ''
except elementpath.ElementPathError as e:
return False, str(e)
except Exception as e:
return False, f'system error: {e}'
Try / catch
try:
elementpath.select(tree, expr, parser=SafeXPath3Parser)
except elementpath.ElementPathError as e:
flash(f"'{expr}' is not a valid XPath expression. ({e})")
Prevention
- Validate XPath in a browser console or XPath tester first
- Keep expressions simple: //tag[@attr='value']
- Remember the parser is XPath 3.0 (elementpath), not libxml2
When it happens
Trigger: A line starting with '/' or 'xpath:' containing malformed XPath 3.0 syntax — unbalanced brackets, invalid axis names, bad predicates — e.g. 'xpath://div[[text()' raises ElementPathError during elementpath.select and this ValidationError is returned.
Common situations: Typos in axes or predicates, XPath 1.0-only habits clashing with the XPath 3.0 parser, unclosed quote inside a predicate, or copying browser $x() expressions with extra wrapping.
Related errors
- XPath not permitted in this field!
- A system-error occurred when validating your XPath expressio
- Invalid JSON object for field: {value}
- A variable or function is not defined: {e}
- The following tokens used in the notification are not valid:
AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27).
Data as JSON: /api/errors/ae697d96bbb084d9.
Report an issue: GitHub.