# Breathe: putting Doxygen output inside Sphinx documentation

> Breathe is a Sphinx plugin that renders Doxygen generated API documentation inside reStructuredText pages, so a project can keep its API reference and its prose in one documentation set. It is used by projects including PyTorch, NumPy, Apache Arrow and Zephyr, and it ships with signed releases and a tool for diffing documentation between commits.

**breathe-doc/breathe** — ReStructuredText and Sphinx bridge to Doxygen

- Repository: https://github.com/breathe-doc/breathe
- Website: https://breathe-doc.org
- Stars: 816 · Forks: 215
- Language: Python
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/breathe-doc-breathe

## Two documentation systems, one page

Breathe is a Sphinx plugin that provides integrated Doxygen output in your user facing documentation. The README describes the intent as combining Doxygen's excellent technical understanding of your code base with the long form documentation output of the Sphinx system, and the tagline is that your technical docs are beautifully integrated.

The problem it solves is a real one for mixed projects. Doxygen parses C and C++ comments and produces a reference that understands the code: every class, every parameter, every overload, in a structured tree. Sphinx understands narrative: guides, tutorials, concepts, cross references. A project that wants both in one place, with one navigation structure and one search index, normally ends up with two sites and a broken link between them.

Breathe closes that gap by letting a reStructuredText page pull in Doxygen content, so an API reference section lives inside a narrative document and is rendered by Sphinx with the same theme, the same sidebar and the same search.

The audience is large projects with substantial C or C++ code and a documentation team, and the list of projects using Breathe is the strongest evidence of the fit: PyTorch, OpenPilot, XGBoost, NumPy, Mozilla's DeepSpeech, Microsoft's LightGBM, PyBind11, Ceph, Apache Arrow, LVGL, the Espressif IoT Development Framework, the Zephyr Project, Plaid ML, Sony's Neural Network Libraries and fmt. These are codebases where the API surface is large enough that hand-maintained reference pages would be hopeless.

## Installing it and the version floor it imposes

Installation is the standard Python package install:

```bash
pip install breathe
```

The dependency surface is deliberately small. The package metadata lists a single runtime dependency, Sphinx with a floor of 7.2, and the project metadata declares requires-python of 3.9 or later. The classifiers spell out the supported interpreter versions from 3.9 through 3.13 and mark the development status as Production/Stable.

The rest of the requirements come from the system rather than from Python. The README states that Breathe requires Python 3.9+, Sphinx 7.2+, and Doxygen 1.9.2+. That Doxygen floor is the one to watch, because it decides whether your existing Doxygen invocation keeps working with the generated XML that Breathe parses.

There are optional extras for people working on the project rather than using it. A build extra pulls in setuptools, a docs extra brings the furo theme, sphinx-copybutton and sphinxcontrib-spelling, and a lint extra carries ruff, mypy, type stubs for docutils and Pygments, pytest and sphinxcontrib-phpdomain. The comment attached to that last dependency explains a dependency subtlety worth knowing: the API Breathe relies on is only implemented in a fork of sphinx-csharp and not in the version published to PyPI.

The build backend is flit_core, and the version is declared as dynamic, so it is read from the package rather than duplicated in the metadata.

## A generated XML parser sits in the build path

The Makefile reveals something about how the project is put together that the README does not spell out: a chunk of Python source is generated rather than written by hand.

The variable GENERATED_MOD points at breathe/_parser.py, and the rule that produces it depends on three files in the xml_parser_generator directory, a schema.json file, a module_template.py.in template and make_parser.py, and it runs a setuptools_builder.py script with python3 to do the work. Several targets depend on that module existing, including the parser target itself, the test targets and the type-check target.

That arrangement means the XML that Doxygen emits is described by a schema, and the parser that consumes it is derived from that schema. The payoff is that a change in the expected XML structure does not require hand editing a large generated file, and the type checker and the test suite both run against the generated code.

There is also a format-parser target that runs ruff format on the generated module, so the generated file is kept in the same formatting style as the rest of the tree, and the type-check target runs mypy with warnings enabled for redundant casts and unused ignores over both the breathe package and the tests.

The project also ships a justfile with a single test target that runs pytest through uv, so the newer tool runner has a one line entry point while the Makefile keeps the fuller set of tasks.

## Running the tests and building the example documentation

The README documents two ways to work with the project locally, and they serve different purposes.

The test suite runs with a single make target:

```bash
make dev-test
```

Looking at the Makefile, dev-test depends on the generated parser and then runs pytest inside the tests directory with PYTHONPATH set to the repository root, while the plain test target runs pytest with a maxfail of one. The distinction is that the development variant runs the suite against your working tree, and the other is the stricter form used for checking.

Building the documentation is the other half, and it is more interesting than a normal documentation build because it exercises the whole pipeline. The default make target builds html and pdf, both of which first run the data target, and the data target builds the three example projects under examples/, namely doxygen, tinyxml and specific. Running Doxygen over the example code is what produces the XML that Breathe then renders.

The result is browsable at documentation/build/html/index.html, and the README notes that the documentation itself makes a good effort of covering the available functionality with different examples, which is another way of saying that the project's own documentation doubles as a gallery of what it can do.

The examples directory therefore has three parts: doxygen for the general case, tinyxml for the parser edge cases, and specific for the hand-written scenarios.

## Diffing documentation output between two commits

One feature deserves a section of its own, because it answers a question every documentation tool eventually has to answer: did my change alter the rendered output?

There is a compare script in the documentation folder. It takes two arguments, which are two commit references you want to compare, and the README notes that this means all your changes have to be committed first. The script does not resolve state dependent references like HEAD, so you have to provide concrete commit references such as sha1s or branch names. The typical example is to compare your current branch output to master:

```bash
cd documentation
./compare master my-branch
```

What it does is a checkout and a build at each commit, and then it runs meld against the resulting directories so you can see the differences introduced by your branch.

The design is sensible for a documentation plugin, because the failure mode it protects against is invisible in the usual way. A change to a docstring, a comment format or a configuration option can silently alter the generated reference without failing a single test, and the person reviewing the pull request sees no difference in the diff because the rendered HTML is not in the repository. Building both revisions and looking at them side by side is the direct way to see what a documentation change actually did.

The dependency on meld for the visual comparison is worth noting, because on a headless CI machine that step needs a different tool or a human.

## Signed releases, changelog discipline and maintainers

The packaging story has one detail that packagers should know about. Breathe packages on PyPI are PGP signed for Breathe v4.28.0 and later, and the tarballs on GitHub are PGP signed for v4.29.0 and later.

The release process is documented in the README and is short. You update CHANGELOG.rst and create the git tag with the version, push the tag to GitHub, and the create-release.yml workflow publishes the release to PyPI. The last step is manual: you go to the tags page on GitHub, select the new tag and use the create release from tag button to publish a GitHub release.

Maintainership is currently held by vermeeren and jakobandersen, who took over from michaeljones and vitaut, and CONTRIBUTORS.rst holds the full list. The project is sponsored through Open Collective, and the README asks for recurring or one-off contributions from companies and individuals that benefit from it.

Support channels have moved over time. The mailing list archives are on Google Groups under sphinx-breathe, with the older librelist.com archives still available, and new questions are asked as GitHub issues rather than on the list.

On versioning, the recent releases show a major bump landing with a series of release candidates, with v5.0.0a2 dated 2025-07-08 and both v5.0.0 and v5.1.0 dated 2026-10-02, and the last push on the same day. A 5.x line is the one to build against for new work.

## Where Breathe is the wrong tool

Three situations point somewhere else.

The first is a project with no Doxygen setup. Breathe renders Doxygen output; it does not generate it. If your C++ code has no Doxygen comments, there is nothing to render, and adopting Breathe means adopting Doxygen first, with its comment conventions and its XML output, before Breathe adds anything. The order matters and the tool does not hide it.

The second is a team that is happy with two documentation sites. The integration is the value proposition, and it is paid for with a dependency on Doxygen's version and output format, a build step in front of Sphinx, and a maintenance burden when the upstream tool changes. A small C library with a small API might reasonably serve a Doxygen site and a separate hand-written guide.

The third is documentation tooling that has moved on. The Sphinx floor of 7.2 and the Python floor of 3.9 are reasonable today, but they are constraints that only move in one direction, and a project pinned to an older Sphinx will be held back by the plugin rather than by its own choices.

One more practical caution is the licence. The package metadata declares the licence as BSD-3-Clause and the classifier agrees, while the repository listing classifies the licence as a custom one that GitHub cannot categorise, and the acknowledgements in the README cover the three upstream projects whose code and design Breathe builds on, Doxygen, Sphinx, and Docutils with reStructuredText. A team with a licence review process should read the LICENSE file rather than trust either label.

## Conclusion

Adopt Breathe if you maintain a C or C++ project with Doxygen comments that also has a Sphinx or reStructuredText documentation set, and you want the API reference and the prose in one build. Do not adopt it if your project has no Doxygen setup to begin with, or if you would rather generate documentation with Doxygen alone and publish the two outputs separately. Verify three things first: the version floor, since Breathe requires Python 3.9 or later, Sphinx 7.2 or later and Doxygen 1.9.2 or later, which constrains your documentation toolchain; how the licence reads for your organisation, because the package metadata declares BSD-3-Clause while the repository listing classifies the licence as a custom one that GitHub cannot categorise, so the LICENSE file is the text to check; and whether the generated XML parser is rebuilt on your platform, since breathe/_parser.py is produced by the xml_parser_generator tooling during the build. The last push was on 2026-10-02 and the newest releases are v5.0.0 and v5.1.0 from the same day.

## FAQ

### What does Breathe do for Sphinx users?

It is a Sphinx plugin that renders Doxygen generated output inside reStructuredText documentation, so the API reference from Doxygen and the long form guides from Sphinx can live in one document set with one theme and one search index.

### How do I install Breathe and what does it require?

Install it with pip install breathe. The README states the requirements as Python 3.9 or later, Sphinx 7.2 or later, and Doxygen 1.9.2 or later, and the package metadata lists Sphinx as its only runtime dependency.

### How do I run the Breathe test suite?

Run make dev-test, which the Makefile implements by building the generated parser module and then running pytest inside the tests directory with the repository root on PYTHONPATH. The plain test target does the same with a maxfail of one.

### Can I check whether my change alters the generated documentation?

Yes. The documentation folder contains a compare script that takes two commit references, builds the documentation at each of them and runs meld against the resulting directories, so you can see the rendered differences introduced by your branch.

### Are the Breathe releases signed?

The PyPI packages are PGP signed for Breathe v4.28.0 and later, and the GitHub tarballs are PGP signed for v4.29.0 and later. The release process updates CHANGELOG.rst, creates and pushes a version tag, and lets the create-release.yml workflow publish to PyPI.

## Sources

- [breathe-doc/breathe on GitHub](https://github.com/breathe-doc/breathe)
- [Issues](https://github.com/breathe-doc/breathe/issues)
- [Project website](https://breathe-doc.org)
- [README](https://github.com/breathe-doc/breathe/blob/main/README.md)
- [Releases](https://github.com/breathe-doc/breathe/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/breathe-doc-breathe
