Registry / web-framework / markupsafe

markupsafe

JSON →
library3.0.3pypypi✓ verified 10d ago

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.

pip install markupsafe
INSTALL
IMPORT
SIG · MARKUPSAFE
M
markupsafe
web-frameworkpythonv3.0.3
Install
1.9s avg
Import
13ms
Disk
16MB
Pass rate
10/ 10
Env Coverage10 / 10
glibc
3.93.13
musl
3.93.13
Install & Compatibility
Where this runs
tested against v3.0.3 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
musl
py 3.103.95 runs
installs and imports cleanly · install 0.0s · import 0.012s · 17.9MB
glibc
py 3.103.95 runs
installs and imports cleanly · install 1.9s · import 0.014s · 18MB
16MB installed
● package 16MB
Code
Verified usage

Verified import paths — ran on the pinned version, not inferred.

Markup
from markupsafe import Markup
from jinja2 import Markup
jinja2.Markup was deprecated in Jinja 3.0 and removed in Jinja 3.1; always import directly from markupsafe.
escape
from markupsafe import escape
from jinja2 import escape
jinja2.escape is a re-export shim that was removed; import escape directly from markupsafe.
soft_str
from markupsafe import soft_str
from markupsafe import soft_unicode
soft_unicode was removed in 2.1.0; use soft_str. Caused widespread ImportError across Jinja2 2.x / Flask 1.x ecosystems.

Escape untrusted user input, build safe HTML with Markup.format(), and check idempotency of escape().

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) # &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt; # 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>&lt;World&gt;</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 &amp; data # Check the version correctly (markupsafe.__version__ is deprecated) import importlib.metadata version = importlib.metadata.version("markupsafe") print(version)
Debug
Known issues
breakingsoft_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'.
fix
Replace all 'from markupsafe import soft_unicode' with 'from markupsafe import soft_str'. Upgrade Jinja2 to >= 3.0 and Flask to >= 2.0.
affects: <2.1.0
breakingPython 3.7 and 3.8 support was dropped in 3.0.0. The minimum required Python version is now 3.9.
fix
Upgrade your Python runtime to 3.9+ before upgrading MarkupSafe to 3.x.
affects: <3.0.0
gotchaMarkup(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.
fix
Use escape(user_input) or Markup.escape(user_input) to escape untrusted strings. Reserve Markup(literal) for hard-coded HTML strings only.
affects: all
gotchaUsing 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.
fix
Use Markup('<b>{}</b>').format(user_input) or Markup('<b>%s</b>') % user_input instead of f-strings inside Markup().
affects: all
gotchastr.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.
fix
Use Markup('<separator>').join(items) instead of '<separator>'.join(items) when mixing Markup and plain strings.
affects: all
deprecatedmarkupsafe.__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.
fix
Use importlib.metadata.version('markupsafe') for version detection.
affects: >=3.0.0
breakingIn 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.
fix
Manually escape any argument to these methods that contains untrusted content before passing it in, or use escape() on the result.
affects: >=3.0.0
Errors
Common errors & fixes
ImportError: cannot import name 'soft_unicode' from 'markupsafe'
The `soft_unicode` function was removed in MarkupSafe version 2.1.0, leading to this error when older versions of dependent libraries (like Jinja2 or Flask) try to import it from newer MarkupSafe installations.
fix
Downgrade MarkupSafe to a compatible version, typically `2.0.1`, or upgrade the dependent library to a version compatible with MarkupSafe 2.1.0+. `pip install markupsafe==2.0.1`
ModuleNotFoundError: No module named 'markupsafe'
The `markupsafe` package is not installed in the Python environment, or the environment where it's installed is not active.
fix
Install the `markupsafe` package using pip: `pip install markupsafe` or `python -m pip install markupsafe`
ImportError: cannot import name 'Markup' from 'jinja2'
In newer versions of Jinja2 (3.0 and above), `Markup` is no longer directly imported from `jinja2` but from `markupsafe`, which is its underlying dependency. Older codebases attempting to import `Markup` directly from `jinja2` will fail.
fix
Change the import statement from `from jinja2 import Markup` to `from markupsafe import Markup`.
TypeError: expected str, bytes or os.PathLike object, not Markup
This error occurs when a function expects a standard string (`str`), bytes, or a path-like object, but instead receives a `markupsafe.Markup` object, which is a subclass of `str` but has special handling to prevent cross-site scripting (XSS). This often happens when `Markup` objects are passed to file I/O operations or other functions not designed to handle them.
fix
Explicitly convert the `Markup` object to a standard string using `str(my_markup_object)` before passing it to the function that expects `str`, bytes, or a path-like object.
AttributeError: 'Markup' object has no attribute 'decode'
This error typically arises when attempting to call the `decode()` method on a `Markup` object in Python 3. In Python 3, `str` (and thus `Markup` which is a subclass of `str`) already represents Unicode, so `decode()` is not a valid operation; it's meant for `bytes` objects. This might be a remnant from Python 2 code or incorrect handling of encoding.
fix
If you intend to work with bytes, ensure the object is indeed a `bytes` object before calling `decode()`. If the `Markup` object contains the desired string, no decoding is needed. If you need to convert it to bytes, use `my_markup_object.encode('utf-8')` instead.
Upgrade
Version history
3.0.3latest on PyPI · released Sep 27, 2025
Audit
Dependencies

No dependency data recorded yet.

Agent activity
15 hits · last 30 days
node
14
Resources
markupsafe — pip install markupsafe · libregistry