Library / SDK
tox-dev/sphinx-autodoc-typehints avatar
tox-dev/sphinx-autodoc-typehints

sphinx-autodoc-typehints: putting Python annotations where Sphinx fields belong

Type hints support for the Sphinx autodoc extension

589 stars114 forksPythonMIT

At a glance

What is it?
This Sphinx extension reads the type hints of the code autodoc documents and writes them into the parameter and return fields, so a type is stated once in the code. It also resolves names imported only under TYPE_CHECKING, documents overload variants and default values, and types Google and NumPy style docstrings.
Who is it for?
sphinx-autodoc-typehints suits a Python project that already annotates its code and wants the documentation to reflect that without maintaining a second type list, particularly one using TypedDict kwargs, Annotated Doc descriptions or overloads, where the built-in setting leaves gaps. It does not suit you if your annotations resolve at runtime and you use Sphinx fields or napoleon docstrings, because the README says the built-in autodoc_typehints setting may be enough.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The claim: state each type once, in the code

The extension is described as one that reads the type hints of the code autodoc documents and writes them into the parameter and return fields, so you state each type once, in the code.

That is the whole pitch, and it is a pitch about removing a duplication. A project that annotates its functions already has the types, in a place the type checker reads. Sphinx's default behaviour puts the annotations in the rendered signature, and the docstring then usually repeats the same information in prose form. The two copies drift, and the drifted one is the docstring.

Beyond the field placement, the extension does several things that the README lists as distinct capabilities. It links every part of a type to its documentation, so a reader can click through to the class they are looking at. It resolves names your code imports only for type checkers, inside an if TYPE_CHECKING: block, or declares in .pyi stub files. It documents @overload variants and default values. And it types Google and NumPy style docstrings written for napoleon or numpydoc.

Installation is a pip install:

bash
pip install sphinx-autodoc-typehints

And enabling it is one line in conf.py, with the ordering requirement stated:

python
# conf.py: load autodoc first, then this extension
extensions = ["sphinx.ext.autodoc", "sphinx_autodoc_typehints"]

The extension has to come after sphinx.ext.autodoc in the list, because it works by rewriting what autodoc has already produced rather than by generating the page itself.

The built-in setting may already be enough, and the README says so

The most useful section of the README for anyone deciding whether to add a dependency is the one that argues against it.

Sphinx's autodoc_typehints = "description" already moves annotations into the parameter and return fields, links them, and works with napoleon docstrings and type comments. And the README states the condition plainly: if your annotations resolve at runtime and you use Sphinx fields or napoleon, that setting may be enough.

That is a useful filter. A project with simple annotations, no TYPE_CHECKING gymnastics, no TypedDict kwargs and no overloads gains very little from this extension. A project that annotates with from __future__ import annotations, where the annotations are strings and names are not resolvable at runtime, gains the most, because the built-in path has to guess.

The README then demonstrates the difference with a concrete example. The same demo.py renders differently with each option, and it uses typing-extensions for Doc. Both builds use the automodule directive for demo with the members flag. The left adds typehints_defaults = "braces" and always_document_param_types = True to the configuration, and the right loads only sphinx.ext.autodoc with autodoc_typehints = "description".

The example code is the reason the difference exists:

python
from __future__ import annotations

from typing import Annotated, Literal, NotRequired, TypedDict, Unpack, overload

from typing_extensions import Doc


class Options(TypedDict):
    """Optional controls for a request."""

    retries: NotRequired[int]
    trace: NotRequired[bool]


@overload
def fetch(path: str, *, mode: Literal["text"], **options: Unpack[Options]) -> str: ...


@overload
def fetch(path: str, *, mode: Literal["bytes"], **options: Unpack[Options]) -> bytes: ...


def fetch(
    path: Annotated[str, Doc("resource path")],
    *,
    mode: Literal["text", "bytes"] = "text",
    **options: Unpack[Options],
) -> str | bytes:
    """Fetch a resource.

    :keyword retries: attempts after a failure
    :keyword trace: log each request
    """
    return "" if mode == "text" else b""

A TypedDict for keyword options, two overloads on a Literal mode, an Annotated Doc on the path parameter and keyword descriptions in the docstring. The built-in Sphinx keeps the overloads in signatures and leaves those keyword descriptions untyped, while this extension puts the overloads in a field, types the retries and trace keywords, and turns the Annotated Doc into a path description.

What the demo module shows, and the constructs it is built from

The demo module the README uses for the comparison is short and every part of it is there to exercise a specific feature.

It starts with from __future__ import annotations, which makes the annotations lazy strings rather than evaluated objects. That single line is what puts the project in the situation the built-in setting handles worst, because the names in the annotations are not available at runtime for Sphinx to resolve on its own.

The typing import brings in Annotated, Literal, NotRequired, TypedDict, Unpack and overload, and typing-extensions is imported for Doc. The Options TypedDict holds two optional controls, retries as NotRequired[int] and trace as NotRequired[bool]. The two overloads of fetch differ only in the Literal value of the mode keyword, text or bytes, and both take the same kwargs typed as Unpack[Options]. The implementation signature widens mode to Literal["text", "bytes"] with a default of "text" and returns str or bytes.

The docstring uses the keyword field syntax, with :keyword retries: attempts after a failure and :keyword trace: log each request. The path parameter carries an Annotated with a Doc holding resource path.

So the module contains, in one file, a lazy annotation import, a TypedDict for keyword options, overloads discriminated by a Literal, an Annotated Doc on a parameter, and keyword descriptions in the docstring body. The comparison image the README links shows the same file built twice, and the caption reads that this extension puts the overloads in a field, types the retries and trace keywords, and turns the Annotated Doc into a path description, while built-in Sphinx keeps the overloads in signatures and leaves those keyword descriptions untyped.

The practical takeaway from the demo is that the gap is not cosmetic. In the built-in rendering the reader has to read a signature containing two overloads to work out the valid mode values, and the keyword options appear as untyped prose. In the extension rendering the overloads are a field, the keyword options are typed, and the path description comes from the annotation.

What each feature actually changes in the rendered output

The README links a set of examples, one per difference, and they are worth reading in order because each one names a specific Python typing feature and what it produces.

Names imported under if TYPE_CHECKING: link to their real module, decimal.Decimal rather than a bare Decimal. That is a small thing that matters a lot in a rendered page, because a bare name in a type field either links nowhere or links to the wrong thing.

Types from .pyi stubs fill in functions that carry no annotations, such as those of a C extension. This is how a project documents a third party library it wraps without patching it.

kwargs typed as Unpack[TypedDict] gets keyword fields typed from the TypedDict entries, which is the case the demo module exists to show.

Annotated[..., Doc(...)] puts descriptions from annotations into the parameter fields, so the path parameter gets the text resource path without it being repeated in the docstring prose.

numpydoc Parameters sections get types in place, rather than in a second list. That last distinction is the one that matters to a numpydoc user, because numpydoc has its own structured Parameters section, and the alternative is a second list of types alongside it.

@overload variants get an Overloads field, and default values appear next to the types. @deprecated notices, @override links and property setter types add details beyond parameter types.

The full comparison page lists all the differences, and the live examples show each module rendered both ways, which is a better way to evaluate the extension than reading a table of differences. There is also a live TypedDict example where you can change the configuration and inspect the output directly.

The packaging, the Python floor, and a fast release cadence

The project metadata tells you how seriously the extension is maintained, and how much churn to expect.

The build backend is hatchling with hatch-vcs, which means the version is derived from the git tags rather than being written by hand, and hatch-fancy-pypi-readme is used to build the long description from a source file. The dynamic field in the project table lists readme and version, which is the marker for both being generated.

The Python floor is 3.12, and the classifiers list 3.12, 3.13, 3.14 and 3.15. The Sphinx requirement is 9.1 or newer, which is a floor worth noting because it means the extension tracks recent Sphinx releases closely. The typing-extensions dependency is conditional, only for Python below 3.15, which tells you that 3.15 has what it needs in the standard library.

The release cadence is fast. Version 3.17.0 was published on 2026-10-17, 3.17.1 the same day later, and 3.17.2 on 2026-10-17 as well, with the last push on 2026-10-19. Three releases in a day is what a project that tracks Sphinx closely looks like, and it has a direct consequence for how you depend on it: pinning is worthwhile.

The development status classifier says Development Status :: 5 - Production/Stable, and the type is marked Typing :: Typed, which is the marker for the package shipping its own type information. The maintainer is Bernát Gábor, and the project is MIT licensed with a LICENSE file in the repository root.

The repository layout is a modern Python project: a src/ directory, a tests/ directory, a docs/ directory, pyproject.toml, tox.toml for the test environments, a uv.lock, a .pre-commit-config.yaml, a .readthedocs.yaml for the documentation build, a .markdownlint.yaml, and two word list files, ignore-words.txt and whitelist.txt, which are the spell checking exceptions for the documentation.

Editorial conclusion

sphinx-autodoc-typehints suits a Python project that already annotates its code and wants the documentation to reflect that without maintaining a second type list, particularly one using TypedDict kwargs, Annotated Doc descriptions or overloads, where the built-in setting leaves gaps. It does not suit you if your annotations resolve at runtime and you use Sphinx fields or napoleon docstrings, because the README says the built-in autodoc_typehints setting may be enough. Before you adopt it, build the same module both ways and compare, because the extension is explicit that the difference shows up in overload placement and keyword typing, and note that it now requires Sphinx 9.1 or newer and Python 3.12 or newer.

Frequently asked questions

How do I install and enable sphinx-autodoc-typehints?

Run pip install sphinx-autodoc-typehints, then add "sphinx.ext.autodoc" and "sphinx_autodoc_typehints" to the extensions list in conf.py, with autodoc loaded first because the extension works by rewriting what autodoc produced.

When is Sphinx's built-in autodoc_typehints enough?

The built-in autodoc_typehints = "description" setting already moves annotations into the parameter and return fields and links them, so if your annotations resolve at runtime and you use Sphinx fields or napoleon docstrings, the README says that setting may be enough.

What does sphinx-autodoc-typehints do with overloads and TypedDict kwargs?

Overload variants get an Overloads field rather than staying in the signature, and **kwargs: Unpack[TypedDict] gets keyword fields typed from the TypedDict entries. The README's demo module shows both differences side by side with the built-in option.

How do I link a type that is only imported under TYPE_CHECKING?

The extension resolves names your code imports only for type checkers inside an if TYPE_CHECKING: block, or declares in .pyi stub files, and links them to their real module, for example decimal.Decimal rather than a bare Decimal.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. tox-dev/sphinx-autodoc-typehints on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tox-dev-sphinx-autodoc-typehints.svg)](https://hysenlabs.com/projects/tox-dev-sphinx-autodoc-typehints)

Was this useful?