# blacken-docs runs Black over the Python snippets in your documentation

> blacken-docs is a small command line tool that finds Python code blocks in Markdown, reStructuredText, LaTeX and docstrings and formats them with Black, so the example in your README cannot drift away from the formatter you use on the rest of the project. It is most useful as a pre-commit hook with Black pinned, and it will not touch a block that is not valid Python.

**adamchainz/blacken-docs** — Run `black` on python code blocks in documentation files

- Repository: https://github.com/adamchainz/blacken-docs
- Stars: 679 · Forks: 46
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/adamchainz-blacken-docs

## A formatter for the examples, not the prose

The one-line description of blacken-docs is the whole idea. It runs Black on Python code blocks in documentation files, and the README's title says the same thing in fewer words: Run `Black` on python code blocks in documentation files.

The problem it solves is the one every Python project hits. Your source is formatted by Black in CI, your README examples are not, and slowly the examples stop matching what the formatter would produce. Then someone copies an example out of the README, pastes it into a file, and the pre-commit hook rewrites it, and the documentation is wrong.

The scope is deliberately narrow. blacken-docs is a command line tool that rewrites documentation files in place, and it supports Markdown, reStructuredText and LaTeX files. Additionally, you can run it on Python files to reformat Markdown and reStructuredText within docstrings, which is the case that catches most people out because the tool also accepts `.py` files.

The history is short and tells you the project has been cared for. It was created by Anthony Sottile in 2018, and at the end of 2022 Adam Johnson took over maintenance, and the package metadata still lists Sottile as the author and Johnson as the maintainer. The licence is MIT, the version is 1.20.0, the supported Python range is 3.11 to 3.15, and Black 22.1.0 or newer is supported.

## Installing it is a pip command

The installation section of the README is short, because the tool is a normal Python package with one runtime dependency.

```
    python -m pip install blacken-docs
```

That is the whole of it. The project metadata shows why it is so light. The package is built with `uv_build` as the build backend, requires Python 3.11 or newer, and has exactly one runtime dependency:

```toml
[project]
name = "blacken-docs"
version = "1.20.0"
description = "Run Black on Python code blocks in documentation files."
readme = "README.rst"
license = "MIT"
license-files = [ "LICENSE" ]
maintainers = [
  { name = "Adam Johnson", email = "me@adamj.eu" },
]
authors = [
  { name = "Anthony Sottile", email = "asottile@umich.edu" },
]
requires-python = ">=3.11"
```

The single dependency is `black>=22.1`, and the console script is registered as `blacken-docs = "blacken_docs:main"`, so the command name is the same as the project name.

One thing the metadata shows that is easy to miss: the repository URL in the package metadata points at `asottile/blacken-docs`, the original author's repository, while the project is now maintained under the `adamchainz` organisation. That is a small sign of a project that changed hands rather than a new project, and it means the GitHub issue tracker you are looking at may not be the one that receives reports.

## As a pre-commit hook, with Black pinned on purpose

The README is clear that the pre-commit integration is the way to use this, and it gives the configuration to copy:

```
    -   repo: https://github.com/adamchainz/blacken-docs
        rev: ""  # replace with latest tag on GitHub
        hooks:
        -   id: blacken-docs
            additional_dependencies:
            - black==26.3.1
```

The `rev` is left empty on purpose, with a comment to replace it with the latest tag on GitHub, which is the convention for a pre-commit hook pinned to a released version rather than a branch.

The `additional_dependencies` line is the important one, and the README explains why. Since Black is a moving target, it is best to pin it in `additional_dependencies`, and upgrade as appropriate. Without that pin, the hook would install whatever Black is current at the moment the hook environment is built, and your documentation formatting would change without any change in your repository.

The README also points at `sync-pre-commit-deps` for the case where you already have Black installed as another hook. That tool automates the upgrade of the pinned Black version so the two hooks cannot drift apart.

With the hook configured, you reformat the entire project with:

```
    pre-commit run blacken-docs --all-files
```

## Files on the command line, and no recursion

The usage section is one of the shortest in the README, and it makes two points that will save you time.

First, you run `blacken-docs` with the filenames to rewrite:

```
    blacken-docs README.rst
```

Second, if any file is modified, `blacken-docs` exits nonzero. That is what makes it work as a CI check as well as a formatter, and it is why the `--check` option exists as a separate mode.

Third, and this is the one that catches people out, blacken-docs does not have any ability to recurse through directories. You have to use the pre-commit integration, globbing, or another technique for applying it to many files. The README's example pipes the file list from git:

```
    git ls-files -z -- '*.md' | xargs -0 blacken-docs
```

The `-z` and `-0` flags are there for a reason, since they keep file names with spaces or unusual characters intact, and the glob restricts the set to Markdown files. The README gives the PowerShell equivalent for Windows users, which pipes `git ls-files` through `ForEach-Object`.

So the shape of the tool is: it does one file at a time, it tells you what it changed by its exit code, and the recursion is somebody else's job. In a repository with a lot of documentation, that means the pre-commit integration is not optional.

## Five Black options pass through, three are its own

Because blacken-docs drives Black, most of Black's configuration is reachable from it.

The options that are passed through to Black are `-l` or `--line-length`, `--preview`, `--pyi`, `-S` or `--skip-string-normalization`, and `-t` or `--target-version`. That list matters when you are deciding whether the hook and your project agree: if your project runs Black with a line length of 88 and the hook does not, the hook will reformat your examples differently from your code, which defeats the purpose of the tool.

The tool then adds three options of its own. `--check` does not modify files but indicates when changes are necessary with a message and a non-zero return code, which is the mode you want in CI. `-E` or `--skip-errors` does not exit non-zero for errors from Black, normally syntax errors. And `--rst-literal-blocks` also formats literal blocks in reStructuredText files.

That last one needs context, which the README provides later on. Literal blocks are marked with `::` and can be any monospaced text by default, but Sphinx interprets them as Python code by default, so if your project uses Sphinx and has that configuration you want the flag, and if you do not have that configuration you do not, because the flag would start reformatting blocks that are not Python at all.

## The block formats it recognises, and the escape hatch

The supported code block formats section is the longest part of the README, and it is mostly a catalogue of patterns.

In Markdown, the tool formats `python` blocks and `pycon` blocks, that is fenced blocks marked with those languages, and the pycon form is the interactive prompt style with `>>>` and `...` continuation markers.

In reStructuredText, it formats `.. code-block:: python` and `.. code-block:: pycon` directives.

In LaTeX, it formats `minted` blocks with the `python` language, using the `\begin{minted}{python}` form.

Within Python files, docstrings that contain Markdown code blocks may be reformatted, and the same applies to docstrings containing reStructuredText code blocks. That is the mechanism behind the earlier statement that you can run the tool on `.py` files.

Each format also has an escape hatch. You can prevent formatting within a block using `blacken-docs:off` and `blacken-docs:on` comments, in the syntax of the format you are in: an HTML comment in Markdown, and a comment directive in reStructuredText. The example shows a block that is deliberately not valid Python, which is exactly the case the escape hatch exists for, and the same mechanism covers the LaTeX flavour.

The practical rule for adding examples: as long as the block is valid Python, blacken-docs will format it, and if you need it left alone, mark it.

## What it will not do, and what the repository looks like

The failure mode to know about is a block that is not valid Python. Black will raise a syntax error, and by default blacken-docs exits non-zero for errors from Black, so a single malformed example in your README fails the hook. The `--skip-errors` flag exists for exactly this case, and the trade-off is obvious: you stop failing on broken examples, and you also stop noticing that they are broken.

The repository is a well maintained Python project. At the top level there are `src/`, `tests/`, `pyproject.toml`, `tox.ini`, `uv.lock`, `README.rst`, `CHANGELOG.rst`, `HISTORY.rst`, `LICENSE`, a `.pre-commit-hooks.yaml` that defines the hook itself, a `.pre-commit-config.yaml` for the project's own hooks, a `.typos.toml`, an `.editorconfig` and a `.github/` directory. The README carries a coverage badge at 100 percent and a CI badge, the test dependency group is pytest with `pytest-randomly` and coverage, and the linter is ruff with a rule selection that includes the usual suspects.

The alternative to this tool is doing the formatting by hand, or using a documentation formatter that also normalises prose. The difference is scope. A prose formatter changes your writing; blacken-docs changes only the code inside your examples, using the same formatter as your code, so the two cannot disagree. If your problem is documentation that drifts away from Black's output, this is the targeted answer, and if your problem is inconsistent prose, it is the wrong tool.

## Conclusion

Adopt blacken-docs when your documentation contains Python examples that you want kept in Black's formatting, and wire it in as a pre-commit hook with a pinned Black version so the two never disagree. Do not adopt it to format prose, since it only rewrites code blocks, and do not expect it to fix a block that is not valid Python, because the failure mode there is a parse error unless you pass `--skip-errors`. Verify first that the Black version you pin in `additional_dependencies` is the one your project actually uses, that the `rev` in the hook points at a real tag, and that the tool's own version matches what you expect, since the package is at 1.20.0 with the last push on 2026-10-05.

## FAQ

### What does blacken-docs do?

It runs Black over the Python code blocks inside documentation files, rewriting the files in place. It supports Markdown, reStructuredText and LaTeX files, and it can also be run on Python files to reformat Markdown or reStructuredText blocks inside docstrings.

### How do I install blacken-docs and use it as a pre-commit hook?

Install it with `python -m pip install blacken-docs`, which pulls in Black 22.1.0 or newer. Add the repository to the repos section of your .pre-commit-config.yaml with the id blacken-docs, pin Black in additional_dependencies, and run `pre-commit run blacken-docs --all-files` to reformat the project.

### Why does the pre-commit config pin the Black version?

The README says that since Black is a moving target, it is best to pin it in additional_dependencies and upgrade as appropriate. Without pinning, the hook environment would pick up whatever Black is current, and your documentation formatting could change without a change in your repository.

### How do I stop blacken-docs from formatting a particular code block?

Use the blacken-docs:off and blacken-docs:on comments around the block, in the syntax of the format you are writing, an HTML comment in Markdown and a comment directive in reStructuredText. That is the mechanism for examples that are deliberately not valid Python.

## Sources

- [adamchainz/blacken-docs on GitHub](https://github.com/adamchainz/blacken-docs)
- [Issues](https://github.com/adamchainz/blacken-docs/issues)
- [License: MIT](https://github.com/adamchainz/blacken-docs/blob/main/LICENSE)
- [README](https://github.com/adamchainz/blacken-docs/blob/main/README.md)

---

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