Safeguard
Developer Tools•

How do you fix PyPI's description failed to render error?

PyPI rejects uploads whose README fails to render. Run twine check, set the readme field and content type correctly, and avoid Sphinx only markup.

Safeguard Security Team
4 min read

Run twine check dist/* on your built files. It reports the same rendering problems PyPI would, usually invalid reStructuredText, an unsupported markup type, or a wrong content type. Fix the README, rebuild, and check again before uploading, because PyPI rejects uploads whose description fails to render.

Why PyPI is strict

The PyPI help page states that PyPI will reject uploads if the package description fails to render. The page you ship is your project's front door, and a broken one is a poor first impression, so the check happens at upload time.

Supported formats

The packaging guide lists what PyPI's README renderer supports.

FormatHow to declare it
Plain textreadme pointing to a text file
reStructuredText without Sphinx extensionsFile ending in .rst
Markdown, GitHub flavored by default, or CommonMarkFile ending in .md

When you use pyproject.toml, the extension of the readme value controls how PyPI renders the description. A readme of README.md renders as Markdown, and README.rst renders as reStructuredText. You can also set the content type explicitly with a table form.

[project]
readme = { file = "README.txt", content-type = "text/markdown" }

Common causes and fixes

  1. Sphinx only markup in reStructuredText. The guide says Sphinx directives and roles, such as py:func or ref roles used in docstrings, are not allowed and produce errors like an unknown interpreted text role. Replace them with plain links or plain text.
  2. Invalid reStructuredText. The guide notes that invalid markup prevents rendering, so PyPI shows the raw source or rejects the file. Fix indentation, heading underline lengths and list formatting.
  3. Wrong content type. If a Markdown file is declared as another type, rendering breaks. Match the declared type to the file.
  4. Old tooling. The guide lists minimum tool versions for GitHub flavored Markdown support in setuptools, wheel and twine. Upgrade them.
  5. README path not included. If the path in pyproject.toml is wrong, the build fails earlier. Check the path relative to pyproject.toml.

Check before you upload

  1. Build with python -m build.
  2. Run twine check dist/*.
  3. Read the output. A good run says Passed for each file.
  4. If it fails, fix the README and rebuild. Do not edit the built archive by hand.

The guide says twine check requires twine 1.12.0 or higher.

Add it to CI

Put the check in your release workflow, so a broken README stops the job before the upload step. This also keeps your TestPyPI rehearsal honest. See how to test a release on TestPyPI.

What belongs in a library README

A short summary of what the library does, an install command, a minimal example, a link to documentation, your security contact and a license note. Keep relative image links out, because they will not resolve on PyPI unless you use full URLs.

Safeguard can run policy checks in your pipeline next to this one. A free scan is a quick way to see what Safeguard reports for your project.

Quick check

Does PyPI accept Markdown READMEs?

Yes. It renders GitHub flavored Markdown by default, or CommonMark if you choose it.

Why do my docstring roles break the README?

PyPI renders reStructuredText without Sphinx extensions, so Sphinx directives and roles are errors there.

How do I check locally?

Build your distributions, then run twine check on the files in dist.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.