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
muslpy 3.10–3.95 runs
installs and imports cleanly · install 0.0s · import 0.012s · 17.9MB
glibcpy 3.10–3.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) # <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)
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.
fixDowngrade 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.
fixInstall 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.
fixChange 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.
fixExplicitly 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.
fixIf 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.