diff --git a/django/forms/jinja2/django/forms/p.html b/django/forms/jinja2/django/forms/p.html
index 999c4d963a..a2872d993e 100644
--- a/django/forms/jinja2/django/forms/p.html
+++ b/django/forms/jinja2/django/forms/p.html
@@ -8,7 +8,7 @@
{% if field.label %}{{ field.label_tag() }}{% endif %}
{{ field }}
{% if field.help_text %}
- {{ field.help_text }}
+ {{ field.help_text|safe }}
{% endif %}
{% if loop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
diff --git a/django/forms/jinja2/django/forms/table.html b/django/forms/jinja2/django/forms/table.html
index 92cd746a49..d1d51f2b12 100644
--- a/django/forms/jinja2/django/forms/table.html
+++ b/django/forms/jinja2/django/forms/table.html
@@ -16,7 +16,7 @@
{{ field }}
{% if field.help_text %}
- {{ field.help_text }}
+ {{ field.help_text|safe }}
{% endif %}
{% if loop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
diff --git a/django/forms/jinja2/django/forms/ul.html b/django/forms/jinja2/django/forms/ul.html
index 116a9b0808..cc4d893c0e 100644
--- a/django/forms/jinja2/django/forms/ul.html
+++ b/django/forms/jinja2/django/forms/ul.html
@@ -12,7 +12,7 @@
{% if field.label %}{{ field.label_tag() }}{% endif %}
{{ field }}
{% if field.help_text %}
- {{ field.help_text }}
+ {{ field.help_text|safe }}
{% endif %}
{% if loop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
diff --git a/django/forms/templates/django/forms/p.html b/django/forms/templates/django/forms/p.html
index 1835b7a461..1346937a34 100644
--- a/django/forms/templates/django/forms/p.html
+++ b/django/forms/templates/django/forms/p.html
@@ -8,7 +8,7 @@
{% if field.label %}{{ field.label_tag }}{% endif %}
{{ field }}
{% if field.help_text %}
- {{ field.help_text }}
+ {{ field.help_text|safe }}
{% endif %}
{% if forloop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
diff --git a/django/forms/templates/django/forms/table.html b/django/forms/templates/django/forms/table.html
index a553776f2f..d4aaafcf53 100644
--- a/django/forms/templates/django/forms/table.html
+++ b/django/forms/templates/django/forms/table.html
@@ -16,7 +16,7 @@
{{ field }}
{% if field.help_text %}
- {{ field.help_text }}
+ {{ field.help_text|safe }}
{% endif %}
{% if forloop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
diff --git a/django/forms/templates/django/forms/ul.html b/django/forms/templates/django/forms/ul.html
index 9ce6a49f07..ae38af6527 100644
--- a/django/forms/templates/django/forms/ul.html
+++ b/django/forms/templates/django/forms/ul.html
@@ -12,7 +12,7 @@
{% if field.label %}{{ field.label_tag }}{% endif %}
{{ field }}
{% if field.help_text %}
- {{ field.help_text }}
+ {{ field.help_text|safe }}
{% endif %}
{% if forloop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
diff --git a/docs/releases/4.0.2.txt b/docs/releases/4.0.2.txt
index b1f1fb9c76..21b1a56884 100644
--- a/docs/releases/4.0.2.txt
+++ b/docs/releases/4.0.2.txt
@@ -11,3 +11,6 @@ Bugfixes
* Fixed a bug in Django 4.0 where ``TestCase.captureOnCommitCallbacks()`` could
execute callbacks multiple times (:ticket:`33410`).
+
+* Fixed a regression in Django 4.0 where ``help_text`` was HTML-escaped in
+ automatically-generated forms (:ticket:`33419`).
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index c478a71699..aa283d53c7 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -2252,6 +2252,41 @@ Password:
"""
)
+ def test_help_text_html_safe(self):
+ """help_text should not be escaped."""
+ class UserRegistration(Form):
+ username = CharField(max_length=10, help_text='e.g., user@example.com')
+ password = CharField(
+ widget=PasswordInput,
+ help_text='Help text is escaped.',
+ )
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(
+ p.as_ul(),
+ '
Username: ' + 'e.g., user@example.com
' + 'Password: ' + 'Help text is escaped.
' + ) + self.assertHTMLEqual( + p.as_table(), + '