MarkupSafe
MarkupSafe implements a text object (Markup, a str subclass) that escapes characters so it is safe to use in HTML and XML. Characters with special meanings are replaced so they display as literal characters, mitigating injection attacks. It is the escaping backbone for Jinja2 and Flask. Current version is 3.0.3 (released Sep 2025); the project follows a feature-release + fix-branch cadence under the Pallets organization, with the 3.0.x branch as the active supported line.
Warnings
- breaking soft_unicode was permanently removed in 2.1.0. Caused mass ImportError in any project depending on Jinja2 < 3 or Flask < 2, which tried 'from markupsafe import soft_unicode'.
- breaking Python 3.7 and 3.8 support was dropped in 3.0.0. The minimum required Python version is now 3.9.
- gotcha Markup(user_input) does NOT escape — the constructor marks a string as already safe without touching its content. Passing dynamic/user-controlled data directly to Markup() is an XSS vulnerability.
- gotcha Using an f-string or %-formatting inside Markup() bypasses escaping: Markup(f'<b>{user_input}</b>') is unsafe. Only Markup.format(), Markup.__mod__, and Markup-level operators auto-escape their arguments.
- gotcha str.join() on a list containing Markup objects does not escape plain-str items in the list. Only Markup.join() escapes non-Markup items in the sequence.
- deprecated markupsafe.__version__ is deprecated since 3.0.0 and now raises DeprecationWarning (upgraded from UserWarning in 3.0.3). Do not read the version via the module attribute.
- breaking In 3.0.0 several Markup str-methods (strip, lstrip, rstrip, removeprefix, removesuffix, partition, rpartition) no longer escape their argument; replace() only escapes its replacement argument. Code relying on those methods escaping search-pattern arguments will silently change behavior.
Install
-
pip install markupsafe
Imports
- Markup
from markupsafe import Markup
- escape
from markupsafe import escape
- soft_str
from markupsafe import soft_str
Quickstart
from markupsafe import Markup, escape
# Escape untrusted input — returns a Markup (str subclass)
user_input = "<script>alert('xss')</script>"
safe = escape(user_input)
print(safe) # <script>alert('xss')</script>
# escape() is idempotent: escaping a Markup object is a no-op
assert escape(safe) == safe
# Build HTML safely: use Markup.format() so arguments are auto-escaped
template = Markup("<p>Hello, <em>{name}</em>!</p>")
html = template.format(name='<World>')
print(html) # <p>Hello, <em><World></em>!</p>
# Join a mixed list safely — use Markup.join(), NOT str.join()
lines = [Markup("<b>Title</b>"), "user & data"]
result = Markup("<br>").join(lines)
print(result) # <b>Title</b><br>user & data
# Check the version correctly (markupsafe.__version__ is deprecated)
import importlib.metadata
version = importlib.metadata.version("markupsafe")
print(version)