Skip to content

How to Build Read the Docs with uv

uv

If your docs already build on Read the Docs, you can now switch .readthedocs.yaml to native uv support instead of maintaining a hand-rolled build.jobs override. Read the Docs added this support on April 21, 2026. The two supported paths are uv sync with a docs dependency group, and uv pip install with a requirements file.

Define a docs dependency group

If your project already has a pyproject.toml, declare a dependency group for the documentation toolchain. Groups are the PEP 735 standard place to keep development-only dependencies out of what your users install:

pyproject.toml
[dependency-groups]
docs = [
    "sphinx>=8.1",
    "furo>=2024.8",
    "myst-parser>=4.0",
]

Pin the versions you actually build with. Dependency groups never ship to PyPI, so they exist only for local and CI installs (see the dependency-groups explainer for the full model).

Wire up .readthedocs.yaml to use uv sync

Create .readthedocs.yaml at the repo root with method: uv and command: sync:

.readthedocs.yaml
version: 2

build:
  os: ubuntu-24.04
  tools:
    python: "3.13"

python:
  install:
    - method: uv
      command: sync
      groups:
        - docs

Read the Docs runs uv sync against your pyproject.toml and pulls in the docs group alongside the project’s main dependencies. If a uv.lock file is present in the repo, uv installs from it; otherwise it resolves fresh.

The groups key takes either a list of group names or the string all. Use all to install every declared group:

python:
  install:
    - method: uv
      command: sync
      groups: all

Pin Python with build.tools.python

Set build.tools.python to an explicit version that satisfies your project’s requires-python, such as "3.13" or "3.14". Avoid "3" unless you’re willing to let doc builds float to the latest 3.x available on Read the Docs.

Commit uv.lock for reproducible doc builds

A single uv.lock covers both the application or library and the docs build. Commit it to version control and Read the Docs installs the exact resolved versions on every build.

$ git add uv.lock
$ git commit -m "Lock dependencies"

Without uv.lock, every build re-resolves and may pull in newer compatible versions of Sphinx, your theme, or any plugin. That works, but a docs build that succeeded yesterday can fail today on a transitive upgrade. See How to use a uv lockfile for reproducible Python environments for the full mechanics of uv lock and uv sync --locked.

Install from a requirements file instead

Projects that aren’t on pyproject.toml yet can still use uv on Read the Docs by switching command to pip:

.readthedocs.yaml
version: 2

build:
  os: ubuntu-24.04
  tools:
    python: "3.13"

python:
  install:
    - method: uv
      command: pip
      requirements: docs/requirements.txt

Read the Docs runs uv pip install -r docs/requirements.txt. Use path: instead of requirements: to install the package itself with optional extras:

python:
  install:
    - method: uv
      command: pip
      path: .
      extras:
        - docs

That maps to uv pip install .[docs].

Avoid the two uv-mode pitfalls

Two rules trip people up on the first migration:

  1. Only one python.install entry is allowed when method: uv. The legacy pip method lets you stack a requirements install and a package install. The uv method does not. Fold everything into a single sync (with groups or extras) or a single pip invocation.
  2. sync and pip accept different keys. groups belongs to command: sync and is rejected under command: pip. requirements belongs to command: pip and is rejected under command: sync. Pick the command that matches the input you have (a pyproject.toml for sync, a requirements.txt for pip) and use only that command’s keys.

Reach for build.jobs when uv-mode is too narrow

Native method: uv covers the common cases but does not surface every uv flag. Workflows that need --locked, --frozen, --no-dev, or a multi-step install have to drop down to build.jobs and call uv directly. See Read the Docs build customization for the escape hatch and stay on python.install for everything else.

Learn more

Last updated on

Please submit corrections and feedback...