# How to Set Up Documentation for a Python Package with Sphinx or MkDocs


[Sphinx](https://pydevtools.com/handbook/reference/sphinx.md) and [MkDocs](https://pydevtools.com/handbook/reference/mkdocs.md) 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](https://pydevtools.com/handbook/reference/uv.md) 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](https://pydevtools.com/handbook/reference/mkdocstrings.md) 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](https://myst-parser.readthedocs.io/) 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](https://pydevtools.com/handbook/reference/mkdocs-material.md) 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](https://zensical.org/) 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](https://pydevtools.com/handbook/explanation/what-is-pep-735.md) `[dependency-groups]` table is the place for it (see [the dependency-groups explainer](https://pydevtools.com/handbook/explanation/understanding-dependency-groups-in-uv.md) for the full model). Open `pyproject.toml` and add one of these:

```toml {filename="pyproject.toml"}
[dependency-groups]
docs = [
    "sphinx>=8",
    "furo",
    "sphinx-autobuild",
]
```

```toml {filename="pyproject.toml"}
[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](https://pydevtools.com/handbook/explanation/src-layout-vs-flat-layout.md) 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:

```bash
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
```
```powershell
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:

```python {filename="docs/source/conf.py"}
html_theme = "furo"
```

The default theme (`alabaster`) ships with Sphinx but looks dated on a 2026 site. [Furo](https://pydevtools.com/handbook/reference/furo.md) 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:

```rst {filename="docs/source/index.rst"}
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:

```console
$ 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](https://pydevtools.com/handbook/reference/sphinx-autobuild.md), which watches `docs/source` and your package source, rebuilds on change, and serves at `http://127.0.0.1:8000`:

```bash
uv run --group docs sphinx-autobuild docs/source docs/build/html --watch src
```

Without `--watch src`, 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:

```console
$ 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:

```yaml {filename="mkdocs.yml"}
site_name: greetlib

theme:
  name: material

plugins:
  - search
  - mkdocstrings:
      handlers:
        python:
          paths: [src]

nav:
  - Home: index.md
  - API reference: api.md
```

`paths: [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:

```markdown {filename="docs/api.md"}
::: greetlib
```

Each `:::` directive is a placeholder for an auto-generated section. mkdocstrings walks `greetlib`, pulls the docstrings, and renders the API reference inline. Build the site:

```console
$ 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`:

```bash
uv run --group docs mkdocs serve
```

Editing 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](https://about.readthedocs.com/) 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](https://pydevtools.com/handbook/how-to/how-to-build-read-the-docs-with-uv.md) 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`](https://github.com/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:

1. Confirm every public function and class your package exposes has rendered with a docstring. A blank entry is usually a missing or malformed docstring.
2. Click through every link in the navigation. Sphinx prints a warning for unresolved references; MkDocs flags broken internal links if you set `strict: true` in `mkdocs.yml`.

## Learn more

- [Sphinx Getting Started](https://www.sphinx-doc.org/en/master/usage/quickstart.html) walks through `sphinx-quickstart` and the `make html` workflow in more depth.
- [Furo theme documentation](https://pradyunsg.me/furo/) covers customization of the recommended Sphinx theme.
- [MkDocs Getting Started](https://www.mkdocs.org/getting-started/) is the canonical reference for `mkdocs new`, `mkdocs build`, and `mkdocs serve`.
- [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) documents theme-level features that change `mkdocs.yml` more than the underlying tool.
- [mkdocstrings Python handler](https://mkdocstrings.github.io/python/usage/) lists every option the `:::` directive accepts.
- [How to Build Read the Docs with uv](https://pydevtools.com/handbook/how-to/how-to-build-read-the-docs-with-uv.md) shows how to host either tool's output.
