How to Set Up Documentation for a Python Package with Sphinx or MkDocs
Sphinx and MkDocs are the two documentation generators most Python packages reach for. They cover the same job (turn Markdown or reStructuredText plus your docstrings into a static HTML site) but optimize for different writers. This guide picks one, scaffolds a docs site inside an existing uv project, and gets a build running locally.
Pick Sphinx or MkDocs
Two questions decide it for most projects.
What’s the dominant content type? API reference dominates in libraries like NumPy and Django, where most pages are autogenerated from docstrings. Sphinx was built for that workload and has the older and broader extension ecosystem around API reference work (intersphinx for cross-project links, sphinx-autodoc-typehints for signature rendering). MkDocs with mkdocstrings covers the API case competently, especially for Python-only projects. MkDocs is friendlier when the docs are prose-first and the API reference is one section among many tutorials and how-tos.
Is reStructuredText acceptable to your contributors? Sphinx’s source format is reStructuredText by default. MyST adds Markdown support, and many projects use it, but the surrounding ecosystem still assumes rST. MkDocs is Markdown-only. If your contributors already write Markdown for READMEs and changelogs, MkDocs removes a syntax that nobody enjoys learning.
A reasonable default: pick MkDocs with Material for MkDocs and mkdocstrings for new projects whose contributors prefer Markdown, and pick Sphinx when the docs are API-first or when you want Sphinx’s broader cross-reference and extension ecosystem (scientific Python and CPython itself standardized on it).
Note
Material for MkDocs entered maintenance mode in early 2026 and a successor called Zensical is reading existing mkdocs.yml files. The MkDocs setup below still works today; expect Zensical to become the migration target later in 2026.
Add a docs dependency group
Whichever tool you pick, isolate the docs toolchain from your runtime dependencies. The PEP 735 [dependency-groups] table is the place for it (see the dependency-groups explainer for the full model). Open pyproject.toml and add one of these:
[dependency-groups]
docs = [
"sphinx>=8",
"furo",
"sphinx-autobuild",
][dependency-groups]
docs = [
"mkdocs>=1.6",
"mkdocs-material",
"mkdocstrings[python]",
]uv add --group docs sphinx furo sphinx-autobuild (or the MkDocs trio) writes the same thing.
The rest of the guide assumes a src layout project that uv init --package produces. Both tools also work with flat layouts; the difference is which paths you point them at.
Set up Sphinx
sphinx-quickstart scaffolds the configuration. Run it from the repo root and point it at a new docs/ directory:
mkdir docs
uv run --group docs sphinx-quickstart docs --quiet --sep --makefile --no-batchfile --project='greetlib' --author='Your Name' --release='0.1.0' --language=en --extensions=sphinx.ext.autodoc,sphinx.ext.napoleon--sep splits source from build. The result is docs/source/conf.py, docs/source/index.rst, and a Makefile. Edit docs/source/conf.py to switch the theme:
html_theme = "furo"The default theme (alabaster) ships with Sphinx but looks dated on a 2026 site. Furo is a drop-in replacement with built-in light and dark themes, and is what pip’s documentation ships.
Replace docs/source/index.rst with a minimal page that pulls in your package:
greetlib
========
A tiny greeting library.
API reference
-------------
.. automodule:: greetlib
:members:The automodule directive imports greetlib, walks its public attributes, and renders each one’s docstring. sphinx.ext.napoleon lets you keep using Google- or NumPy-style docstrings instead of switching to native rST. Build the site:
$ uv run --group docs sphinx-build -b html docs/source docs/build/html
...
build succeeded.
Open docs/build/html/index.html in a browser to confirm the API section has rendered every public function. While editing, swap sphinx-build for sphinx-autobuild, which watches docs/source and your package source, rebuilds on change, and serves at http://127.0.0.1:8000:
uv run --group docs sphinx-autobuild docs/source docs/build/html --watch src--watch src is the load-bearing flag. Without it, edits to your package’s docstrings don’t trigger a rebuild because Sphinx considers them outside the documentation source tree.
Set up MkDocs
mkdocs new . scaffolds the same way, but in-place rather than under a docs/ subdirectory:
$ uv run --group docs mkdocs new .
INFO - Writing config file: ./mkdocs.yml
INFO - Writing initial docs: ./docs/index.md
You get an mkdocs.yml at the repo root and a docs/ directory with index.md. Replace mkdocs.yml with a configuration that picks the Material theme and wires up mkdocstrings to render the package:
site_name: greetlib
theme:
name: material
plugins:
- search
- mkdocstrings:
handlers:
python:
paths: [src]
nav:
- Home: index.md
- API reference: api.mdpaths: [src] tells the Python handler where to find your package on disk. With a flat layout, drop the key entirely or set it to [.].
Create docs/api.md with one line:
::: greetlibEach ::: directive is a placeholder for an auto-generated section. mkdocstrings walks greetlib, pulls the docstrings, and renders the API reference inline. Build the site:
$ uv run --group docs mkdocs build
INFO - Cleaning site directory
INFO - Building documentation to directory: ./site
INFO - Documentation built in 0.33 seconds
mkdocs serve runs a live-reload dev server at http://127.0.0.1:8000:
uv run --group docs mkdocs serveEditing either a Markdown page or your package source triggers a rebuild.
Host the result
Local builds are enough to review changes; long-lived URLs need a host. Two paths cover almost every project:
Read the Docs is the standard host for open-source Python packages. It builds on every push, serves versioned URLs (latest, stable, plus a URL per release tag), and runs both Sphinx and MkDocs. See How to Build Read the Docs with uv for the .readthedocs.yaml configuration now that Read the Docs supports uv natively (added April 2026).
GitHub Pages publishes the build output to <user>.github.io/<repo> from a branch or directly from a GitHub Actions workflow. MkDocs ships mkdocs gh-deploy, which pushes the built site/ to a gh-pages branch in one command. Sphinx has no built-in equivalent; use the peaceiris/actions-gh-pages action or push docs/build/html from a workflow.
Verify the rendered site
Before pointing a host at the repo, run the build cleanly one more time and skim the output:
- Confirm every public function and class your package exposes has rendered with a docstring. A blank entry is usually a missing or malformed docstring.
- Click through every link in the navigation. Sphinx prints a warning for unresolved references; MkDocs flags broken internal links if you set
strict: trueinmkdocs.yml.
Learn more
- Sphinx Getting Started walks through
sphinx-quickstartand themake htmlworkflow in more depth. - Furo theme documentation covers customization of the recommended Sphinx theme.
- MkDocs Getting Started is the canonical reference for
mkdocs new,mkdocs build, andmkdocs serve. - Material for MkDocs documents theme-level features that change
mkdocs.ymlmore than the underlying tool. - mkdocstrings Python handler lists every option the
:::directive accepts. - How to Build Read the Docs with uv shows how to host either tool’s output.