Docs & Code Guide

Embed a diagram in a PyPI project page: what readme_renderer keeps

By the engineer who builds Datadef, from client work on data platforms · Reviewed August 21, 2026

PyPI is stricter than a git host about what it will show, and less forgiving about mistakes: the project description is fixed for a release, so an image that fails to render stays broken until the next version goes out. The rules are knowable in advance. This page covers what the renderer keeps, why relative paths never work there, and how to check before uploading rather than after.

6 min readFor maintainers of published Python packages

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

120/20003 credits left
Try:

No account needed · Editable canvas, not a picture

The renderer and its allowlist

PyPI renders long_description with readme_renderer, the library the Warehouse codebase uses for exactly this, and cleans the resulting HTML against an allowlist. Script, style and iframe elements do not survive that pass. The img element does, which is what makes a rendered diagram possible on a project page at all.

The content type has to match the markup. Set long_description_content_type to text/markdown for a Markdown README, or text/x-rst for reStructuredText. Get it wrong and PyPI displays the description as preformatted plain text, image line included, which is a distinctive and easily recognised failure.

There is no interactive option here. Put the image on the page and a link next to it for anyone who wants pan and zoom in a browser tab.

Relative paths have nothing to resolve against

A PyPI project page has no repository context. There is no branch, no raw host, no base URL to complete ./docs/architecture.png into something fetchable, so a relative image is a request for a file that does not exist on pypi.org. GitHub makes this easy to miss, because the same README renders correctly there.

Use an absolute https address. The https part matters as much as the absolute part: project pages are served over https, so an http image is blocked as mixed content by every current browser and shows nothing.

The same rule applies to badges, screenshots and any linked asset in the description. One absolute-URL policy for the whole README file removes an entire class of release-day embarrassment.

<!-- Renders on GitHub, blank on PyPI -->
![Architecture](docs/architecture.png)

<!-- Renders on both, and keeps up between releases -->
![Architecture](https://datadef.io/api/embed/my-package-a1b2c3d4?width=1600&height=900)

The description is fixed for the release

PyPI does not accept a re-upload of a version that already exists, so a description cannot be corrected in place. A broken image line lives on the project page until the next release, or until someone takes the drastic route of deleting the release entirely.

That constraint is the argument for an image URL rather than a committed file, sharpened. The description text is frozen the moment the version is uploaded, and the picture behind an address is not. A project whose last release was six months ago can still show what its architecture looks like this week.

Run twine check on the built distribution before uploading. It runs the description through readme_renderer and fails on markup PyPI would reject, which catches the content-type mistake and malformed markup before they become permanent.

python -m build
python -m twine check dist/*

What the diagram should show for a library

A package page reader is deciding whether this library fits their system, so the useful picture is the runtime one: what the package connects to, which services it calls, where data ends up. A module tree answers a question nobody is asking at that moment.

For a package that wraps a platform, showing the platform is often the point. Datadef ships 2,098 provider icons as individual SVGs: 439 for AWS, 624 for Azure, 226 for Google Cloud, and the remainder across data tooling, Kubernetes, networking and general infrastructure. A wrapper around Snowflake or Kafka draws with the Snowflake and Kafka marks, so the picture on the project page reads as the systems it names rather than as a row of grey boxes with words in them.

Keeping it current without a release

Connect the repository read-only from GitHub, GitLab or Azure DevOps, choose a branch or tag, and the sync regenerates the diagram and an architecture.md daily. The image behind the address on your project page follows, no upload involved.

The image endpoint sends Cache-Control public, max-age=300, stale-while-revalidate=86400, and an ETag built from the canvas id, its updated timestamp and the requested size, so an edited diagram reaches readers within about five minutes, a reader who arrives during the next twenty-four hours is served the cached render while a fresh one is built behind them, and a repeat visitor gets a 304 rather than a re-render. The same address belongs in the repository README and in the docs site, so all three agree. See embedding documentation.

FAQ

Why is my README image not showing on PyPI?

The two usual causes are a relative path, which has nothing to resolve against on a project page, and an http address, which browsers block as mixed content on an https page. Use an absolute https URL. A third cause is a wrong long_description_content_type, which makes the whole description render as plain text.

Can I fix a broken image in a PyPI description without a new release?

No. PyPI refuses re-uploads of an existing version, so the description is fixed once the release is up. Run twine check before uploading, which renders the description through readme_renderer and fails on markup PyPI would reject. The only other route is deleting the release entirely, which breaks anyone who already pinned it. Build with python -m build, then check dist/* before touching upload.

Does PyPI allow iframes or scripts in a project description?

No. The rendered HTML is cleaned against an allowlist that drops script, style and iframe elements. Images survive, so a rendered diagram plus a link to an interactive view is the workable combination. PyPI renders long_description with readme_renderer, the same library the Warehouse codebase uses, so what that library keeps is exactly what reaches the page.

Can a PyPI project page show a diagram that changes after the release?

Yes, when the image is served from an address rather than committed. The description text freezes at upload, while an image URL returns whatever it returns at read time, so the picture can track the code between releases. For a package whose last release was six months ago, that is the difference between a project page showing this week's architecture and one showing the architecture of six months ago.

Do readers need an account to view the linked diagram?

No. A publicly shared project serves an image URL and a chrome-less interactive page, both open to anyone with the link and neither requiring a sign-in. That is what makes the pattern work on a public project page, where most readers have no relationship with your tooling. It also means anyone with the address can view the diagram, so keep internal hostnames and account identifiers out of it.