Library / SDK
adamchainz/blacken-docs avatar
adamchainz/blacken-docs

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

Run `black` on python code blocks in documentation files

679 stars46 forksPythonMIT

At a glance

What is it?
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.
Who is it for?
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`.
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 19 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 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

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.

code
    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 = "[email protected]" },
]
authors = [
  { name = "Anthony Sottile", email = "[email protected]" },
]
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:

code
    -   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:

code
    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:

code
    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:

code
    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.

Editorial 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.

Frequently asked questions

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.

Official sources

  1. adamchainz/blacken-docs on GitHub
  2. Issues
  3. License: MIT
  4. README
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/adamchainz-blacken-docs.svg)](https://hysenlabs.com/projects/adamchainz-blacken-docs)

Was this useful?