dgtlmoon/changedetection.io · warning · ValidationError

The following tokens used in the notification are not valid:

Error message

The following tokens used in the notification are not valid: {undefined}

What it means

After successfully rendering the notification template, the validator statically parses it with jinja2_env.parse() and runs jinja2.meta.find_undeclared_variables on the AST. Any variable name found in the template that is not a known notification token is reported back as a comma-joined list in a wtforms.ValidationError. This is a stricter check than runtime UndefinedError: it catches variables even if the undefined policy would tolerate them.

Source

Thrown at changedetectionio/forms.py:603

            static_token_placeholders = NotificationContextData()
            static_token_placeholders.set_random_for_validation()
            jinja2_env.globals.update(static_token_placeholders)
            if hasattr(field, 'extra_notification_tokens'):
                jinja2_env.globals.update(field.extra_notification_tokens)

            jinja2_env.from_string(joined_data).render()
        except TemplateSyntaxError as e:
            raise ValidationError(f"This is not a valid Jinja2 template: {e}") from e
        except UndefinedError as e:
            raise ValidationError(f"A variable or function is not defined: {e}") from e
        except jinja2.exceptions.SecurityError as e:
            raise ValidationError(f"This is not a valid Jinja2 template: {e}") from e

        # Check for undeclared variables
        ast = jinja2_env.parse(joined_data)
        undefined = ", ".join(find_undeclared_variables(ast))
        if undefined:
            raise ValidationError(
                f"The following tokens used in the notification are not valid: {undefined}"
            )

class validateURL(object):

    """
       Flask wtform validators wont work with basic auth
    """

    def __init__(self, message=None):
        self.message = message

    def __call__(self, form, field):
        # This should raise a ValidationError() or not
        validate_url(field.data)


def validate_url(test_url):

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Compare each name in the error list against the documented notification tokens and fix typos
  2. Remove unused/unknown placeholders from the template
  3. If a custom token is genuinely needed, add it to extra_notification_tokens for the field before validation

Example fix

# before
{{watch_ur}}
# after
{{watch_url}}
Defensive patterns

Strategy: validation

Validate before calling

from jinja2 import Environment
from jinja2.meta import find_undeclared_variables

def undeclared(template: str, known: set[str]) -> set[str]:
    ast = Environment().parse(template)
    return find_undeclared_variables(ast) - known

Prevention

When it happens

Trigger: A template that renders without error but references names not declared in the environment, e.g. {{my_custom_var}} where only tokens from field.extra_notification_tokens exist. find_undeclared_variables(ast) returns a non-empty set, so the form is rejected with the offending names listed.

Common situations: Typos in token names, tokens valid only in other notification contexts (e.g. AppRise/JSON placeholders), or using variables the sandbox does not predeclare (like loop or var names not set).

Related errors


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/1244b686bde094c7. Report an issue: GitHub.