{"id":265,"library":"markupsafe","title":"MarkupSafe","description":"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.","status":"active","version":"3.0.3","language":"python","source_language":"en","source_url":"https://github.com/pallets/markupsafe","tags":["html","xml","escaping","security","xss","jinja2","flask","templating","pallets"],"install":[{"cmd":"pip install markupsafe","lang":"bash","label":"Latest (3.0.x)"}],"dependencies":[],"imports":[{"note":"jinja2.Markup was deprecated in Jinja 3.0 and removed in Jinja 3.1; always import directly from markupsafe.","wrong":"from jinja2 import Markup","symbol":"Markup","correct":"from markupsafe import Markup"},{"note":"jinja2.escape is a re-export shim that was removed; import escape directly from markupsafe.","wrong":"from jinja2 import escape","symbol":"escape","correct":"from markupsafe import escape"},{"note":"soft_unicode was removed in 2.1.0; use soft_str. Caused widespread ImportError across Jinja2 2.x / Flask 1.x ecosystems.","wrong":"from markupsafe import soft_unicode","symbol":"soft_str","correct":"from markupsafe import soft_str"}],"quickstart":{"code":"from markupsafe import Markup, escape\n\n# Escape untrusted input — returns a Markup (str subclass)\nuser_input = \"<script>alert('xss')</script>\"\nsafe = escape(user_input)\nprint(safe)  # &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;\n\n# escape() is idempotent: escaping a Markup object is a no-op\nassert escape(safe) == safe\n\n# Build HTML safely: use Markup.format() so arguments are auto-escaped\ntemplate = Markup(\"<p>Hello, <em>{name}</em>!</p>\")\nhtml = template.format(name='<World>')\nprint(html)  # <p>Hello, <em>&lt;World&gt;</em>!</p>\n\n# Join a mixed list safely — use Markup.join(), NOT str.join()\nlines = [Markup(\"<b>Title</b>\"), \"user & data\"]\nresult = Markup(\"<br>\").join(lines)\nprint(result)  # <b>Title</b><br>user &amp; data\n\n# Check the version correctly (markupsafe.__version__ is deprecated)\nimport importlib.metadata\nversion = importlib.metadata.version(\"markupsafe\")\nprint(version)\n","lang":"python","description":"Escape untrusted user input, build safe HTML with Markup.format(), and check idempotency of escape()."},"warnings":[{"fix":"Replace all 'from markupsafe import soft_unicode' with 'from markupsafe import soft_str'. Upgrade Jinja2 to >= 3.0 and Flask to >= 2.0.","message":"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'.","severity":"breaking","affected_versions":"<2.1.0"},{"fix":"Upgrade your Python runtime to 3.9+ before upgrading MarkupSafe to 3.x.","message":"Python 3.7 and 3.8 support was dropped in 3.0.0. The minimum required Python version is now 3.9.","severity":"breaking","affected_versions":"<3.0.0"},{"fix":"Use escape(user_input) or Markup.escape(user_input) to escape untrusted strings. Reserve Markup(literal) for hard-coded HTML strings only.","message":"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.","severity":"gotcha","affected_versions":"all"},{"fix":"Use Markup('<b>{}</b>').format(user_input) or Markup('<b>%s</b>') % user_input instead of f-strings inside Markup().","message":"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.","severity":"gotcha","affected_versions":"all"},{"fix":"Use Markup('<separator>').join(items) instead of '<separator>'.join(items) when mixing Markup and plain strings.","message":"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.","severity":"gotcha","affected_versions":"all"},{"fix":"Use importlib.metadata.version('markupsafe') for version detection.","message":"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.","severity":"deprecated","affected_versions":">=3.0.0"},{"fix":"Manually escape any argument to these methods that contains untrusted content before passing it in, or use escape() on the result.","message":"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.","severity":"breaking","affected_versions":">=3.0.0"}],"env_vars":null,"last_verified":"2026-05-12T12:37:34.490Z","next_check":"2026-06-26T00:00:00.000Z","problems":[{"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`","cause":"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.","error":"ImportError: cannot import name 'soft_unicode' from 'markupsafe'"},{"fix":"Install the `markupsafe` package using pip: `pip install markupsafe` or `python -m pip install markupsafe`","cause":"The `markupsafe` package is not installed in the Python environment, or the environment where it's installed is not active.","error":"ModuleNotFoundError: No module named 'markupsafe'"},{"fix":"Change the import statement from `from jinja2 import Markup` to `from markupsafe import Markup`.","cause":"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.","error":"ImportError: cannot import name 'Markup' from 'jinja2'"},{"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.","cause":"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.","error":"TypeError: expected str, bytes or os.PathLike object, not Markup"},{"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.","cause":"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.","error":"AttributeError: 'Markup' object has no attribute 'decode'"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":80,"quickstart_tag":"verified","pypi_latest":null,"install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.6,"disk_size":"17.9M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.6,"disk_size":"18M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":0.7,"disk_size":"19.7M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":0.7,"disk_size":"20M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":0.7,"disk_size":"11.6M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.7,"disk_size":"12M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.8,"disk_size":"11.2M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.6,"disk_size":"12M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.5,"disk_size":"17.4M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.5,"disk_size":"18M"}]},"quickstart_checks":{"last_tested":"2026-04-23","tag":"verified","tag_description":"quickstart runs on critical runtimes, recently tested","results":[{"runtime":"python:3.10-alpine","exit_code":0},{"runtime":"python:3.10-slim","exit_code":0},{"runtime":"python:3.11-alpine","exit_code":0},{"runtime":"python:3.11-slim","exit_code":0},{"runtime":"python:3.12-alpine","exit_code":0},{"runtime":"python:3.12-slim","exit_code":0},{"runtime":"python:3.13-alpine","exit_code":0},{"runtime":"python:3.13-slim","exit_code":0},{"runtime":"python:3.9-alpine","exit_code":0},{"runtime":"python:3.9-slim","exit_code":0}]}}