{"record":{"id":"1244b686bde094c7","repo":"dgtlmoon/changedetection.io","slug":"the-following-tokens-used-in-the-notification-are","errorCode":null,"errorMessage":"The following tokens used in the notification are not valid: {undefined}","messagePattern":"The following tokens used in the notification are not valid: (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"warning","filePath":"changedetectionio/forms.py","lineNumber":603,"sourceCode":"            static_token_placeholders = NotificationContextData()\n            static_token_placeholders.set_random_for_validation()\n            jinja2_env.globals.update(static_token_placeholders)\n            if hasattr(field, 'extra_notification_tokens'):\n                jinja2_env.globals.update(field.extra_notification_tokens)\n\n            jinja2_env.from_string(joined_data).render()\n        except TemplateSyntaxError as e:\n            raise ValidationError(f\"This is not a valid Jinja2 template: {e}\") from e\n        except UndefinedError as e:\n            raise ValidationError(f\"A variable or function is not defined: {e}\") from e\n        except jinja2.exceptions.SecurityError as e:\n            raise ValidationError(f\"This is not a valid Jinja2 template: {e}\") from e\n\n        # Check for undeclared variables\n        ast = jinja2_env.parse(joined_data)\n        undefined = \", \".join(find_undeclared_variables(ast))\n        if undefined:\n            raise ValidationError(\n                f\"The following tokens used in the notification are not valid: {undefined}\"\n            )\n\nclass validateURL(object):\n\n    \"\"\"\n       Flask wtform validators wont work with basic auth\n    \"\"\"\n\n    def __init__(self, message=None):\n        self.message = message\n\n    def __call__(self, form, field):\n        # This should raise a ValidationError() or not\n        validate_url(field.data)\n\n\ndef validate_url(test_url):","sourceCodeStart":585,"sourceCodeEnd":621,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/forms.py#L585-L621","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Compare each name in the error list against the documented notification tokens and fix typos","Remove unused/unknown placeholders from the template","If a custom token is genuinely needed, add it to extra_notification_tokens for the field before validation"],"exampleFix":"# before\n{{watch_ur}}\n# after\n{{watch_url}}\n","handlingStrategy":"validation","validationCode":"from jinja2 import Environment\nfrom jinja2.meta import find_undeclared_variables\n\ndef undeclared(template: str, known: set[str]) -> set[str]:\n    ast = Environment().parse(template)\n    return find_undeclared_variables(ast) - known\n","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Publish the exact token list next to the template editor","Automate token autocompletion from extra_notification_tokens","Reject templates early client-side using find_undeclared_variables"],"tags":["jinja2","undeclared-variables","wtforms","changedetectionio","notifications"],"backgroundTag":"jinja2-undefined-variable","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}