# nbQA: running the linters on Jupyter notebooks

> nbQA wraps the usual Python linters and formatters so they can be pointed at a notebook. Black, isort, pyupgrade, mypy, pylint, flake8, ruff, doctest and the rest all work on a .ipynb file, and the same wrapper plugs into pre-commit so a notebook can be linted the way a source file is.

**nbQA-dev/nbQA** — Run ruff, isort, pyupgrade, mypy, pylint, flake8, and more on Jupyter Notebooks

- Repository: https://github.com/nbQA-dev/nbQA
- Website: https://nbqa.readthedocs.io/en/latest/index.html
- Stars: 1,205 · Forks: 51
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/nbqa-dev-nbqa

## Why a notebook needs a wrapper at all

The problem is structural. A Jupyter notebook is a JSON document with source code, outputs, execution counts and metadata, and a linter expects a Python file. Every tool in the Python quality ecosystem assumes the latter, so the ecosystem as a whole skips notebooks entirely.

That is why notebooks in a linted repository tend to drift. The module gets formatted, the type checker sees it, the linter complains about it, and the notebook sits next to it in a different style with different rules, and nobody notices because there is no tool in the pipeline looking at it.

nbQA is the wrapper that closes that gap. The name stands for notebook quality assurance, and the readme's summary line is a list of the tools it drives: ruff, isort, pyupgrade, mypy, pylint, flake8, black, blacken-docs and more.

The interesting part is not the list, it is the four guarantees the readme puts at the top, because they are the four things that make a wrapper like this hard. It handles IPython magics robustly, so the lines that are not Python do not confuse the parser. It respects your configuration files, so the settings you have already tuned for your source tree apply to the notebook too. It preserves the trailing semicolons that quiet mode depends on, which is the detail that would otherwise silently break a notebook whose output you have chosen to suppress. And it lints both code and markdown cells, which matters because a notebook is documentation as much as it is code.

That last one is a design decision rather than a technicality. A notebook that renders as prose in a notebook viewer has markdown cells, and those cells have their own formatting rules, and the blacken-docs integration exists to apply them.

## Installing it, and the toolchain extra

The installation is a pip line, and the readme asks for it inside a virtual environment.

```bash
$ python -m pip install -U nbqa
```

There is a second option, and the difference matters. To install all the supported linters and formatters as well:

```bash
$ python -m pip install -U "nbqa[toolchain]"
```

So the base package is the wrapper, and the extra pulls in the tools themselves. That is a sensible split for a project like this, because the wrapper is small and the toolchain is large, and because most users want one or two tools rather than all of them.

The package is published on PyPI as nbqa, the documentation is hosted on Read the Docs, and the badges show a tox workflow, a coverage report on Codecov and the pre-commit continuous integration results, which tells you all three are part of the build.

The project is MIT licensed, and the repository carries a licences directory alongside the licence file, which is the pattern for a project that vendors or redistributes code with its own terms.

## The command surface, one tool at a time

The usage examples are the most convincing part of the readme, because the output of each command is shown.

Black reformats the notebook and reports what it did:

```bash
$ nbqa black my_notebook.ipynb
```

Isort sorts the imports, and the example passes the flag that moves the import block to the top of the cell rather than below the docstring:

```bash
$ nbqa isort my_notebook.ipynb --float-to-top
```

Pyupgrade rewrites the syntax to a newer target, and the flag names the Python version you are aiming at:

```bash
$ nbqa pyupgrade my_notebook.ipynb --py37-plus
```

And blacken-docs formats the markdown cells, with a flag that says to format the markdown and a flag that shows a diff instead of applying it:

```bash
$ nbqa blacken-docs my_notebook.ipynb --nbqa-md --nbqa-diff
```

The diff output in the readme is a unified diff against the notebook, and the readme tells you how to apply the change, by removing the diff flag.

There is a neat detail in the last example: the same command works on a markdown file saved through Jupytext, if jupytext is installed. That is the Jupytext integration, and it means a notebook that lives in a repository as plain markdown can be formatted with the same command as one that lives as a notebook.

The documentation lists more tools with examples, among them doctest, flake8, mypy, pylint, autopep8, pydocstyle, yapf and ruff. The doctest one is the most valuable of the additional tools, because it means the examples inside a notebook's docstrings are actually run.

## Pre-commit, and pinning the tool versions

The pre-commit integration is what turns this from a tool people remember to run into a rule that is always applied.

The configuration is a normal pre-commit block, with the repository, a revision and the hooks:

```yaml
- repo: https://github.com/nbQA-dev/nbQA
  rev: 1.9.1
  hooks:
    - id: nbqa-black
      additional_dependencies: [jupytext]  # optional, only if you're using Jupytext
    - id: nbqa-pyupgrade
      args: ["--py37-plus"]
    - id: nbqa-isort
      args: ["--float-to-top"]
```

Two things are worth copying verbatim. The additional dependencies entry, which is how the Jupytext support is enabled for the people who need it, and the pinned revision of the wrapper itself, which is the pre-commit convention anyway.

The readme also explains how to select specific versions of the linters and formatters, by adding them to the additional dependencies list. That matters because the default behaviour is to use whatever version of Black the environment happens to have, and Black's output changes between releases, so a repository that wants a stable formatting result has to pin the formatter as well as the wrapper.

The hook ids follow a pattern, one per tool with the tool name as the prefix, which means a repository can enable Black and isort and leave the type checker to a different job if it wants to.

The project publishes its own pre-commit continuous integration results as a badge, so the hooks it ships are themselves exercised on every change.

## Who is already using it

The readme carries a long list of repositories that use the tool, and it is worth scanning because the list is a sample of the kinds of project that care.

They are scientific and research code. There is the Alan Turing Institute's sktime, the glotzerlab examples for freud, hoomd and signac, the PyMC examples and resources, the openff-system, the primap2 climate accounting project, the pandas-profiling project, and the climate attribution repository from the covid-19 impact lab. There is the Google Cloud Platform examples repository and the OpenMined privacy project.

The pattern is consistent. These are codebases where a notebook is a first class artifact rather than a scratchpad, where the outputs are part of the deliverable, and where the people writing the notebooks are researchers who did not necessarily come from a software engineering background and would not otherwise have a formatter configured.

The list is also long enough to be evidence that the wrapper handles a variety of notebook styles without falling over, which is the risk in any tool that parses a format it did not define. A repository that uses Black through nbQA has implicitly decided that its notebooks are source code, and the fact that dozens of projects have made that decision without the wrapper falling over is a reasonable signal.

The testimonials section and the Gitter chat link are the other two pieces of the readme that speak to adoption rather than to features, and the download badge points at the PyPI statistics.

## The engineering behind the wrapper

The project configuration shows the standards the project holds itself to, and they are strict in a way that is easy to copy.

The docstring coverage tool is configured to fail under one hundred percent, with the initialisation method ignored, which means every public function has a docstring or the build fails. The import sorting is configured with the black profile, so the two tools agree on how imports are formatted. Coverage runs with branch coverage enabled and reports the lines that are excluded, with a specific set of patterns for pragma comments, not implemented errors, main guards, type checking blocks and assertion errors, which is the standard way to stop defensive code from counting against you.

The Pylint configuration is the most interesting file, because every disabled message has a reason written next to it. The naming convention check is disabled because snake case for methods is needed for subclassing the abstract syntax tree classes. The line length check is off because Flake8 already covers it. The duplicate code check is off because the author does need some duplication, for example with imports, and the too few public methods check is off for the same kind of reason. The subprocess call without a check is allowed because a failing subprocess is acceptable in that case, and the two import errors are about typing stubs that do not matter for type checking.

A short list like that, with the reasoning attached, tells you more about how a maintainer thinks than a policy document would.

The packaging is minimal, with a setup file that just calls setup, and the project metadata lives in the pyproject file alongside the tool configuration. The test suite runs under tox, and the repository has a separate pre-commit configuration for its own source, which is the same tool it asks its users to adopt.

## Conclusion

nbQA suits a team that has adopted strict Python style rules and wants notebooks held to the same standard as its modules, especially since the pre-commit integration means the rules apply without anyone remembering to run them. It is a poor fit if you have no opinion about notebook style, since the value of the tool is entirely in the policy it enforces, and a poor fit if you rely on magics in ways the wrapper cannot parse. Before you adopt it, check that the linters you already use read the same configuration files, since the readme makes respecting your config files one of its guarantees, and pin the versions in the pre-commit hook if you need reproducible formatting.

## FAQ

### How do I install nbQA?

Inside a virtual environment, run python -m pip install -U nbqa. To pull in all the supported linters and formatters as well, install the toolchain extra with python -m pip install -U "nbqa[toolchain]".

### Which linters can nbQA run on a notebook?

The readme lists ruff, isort, pyupgrade, mypy, pylint, flake8, black, blacken-docs, doctest, autopep8, pydocstyle and yapf, with command line examples for each in the documentation. The wrapper is invoked as nbqa followed by the tool name and the notebook path.

### How do I set up nbQA as a pre-commit hook?

Add a block to your pre-commit config pointing at the nbQA repository with a pinned revision, and list the hooks you want by id, such as nbqa-black, nbqa-pyupgrade and nbqa-isort. Arguments go in the args list, and extra packages such as jupytext go in additional_dependencies.

### Does nbQA work with notebooks stored as markdown?

Yes, through Jupytext. With jupytext installed, the same commands work on a markdown file, for example nbqa black on a .md file, and the readme notes that the pre-commit configuration needs the jupytext package in additional_dependencies for that to work.

## Sources

- [License: MIT](https://github.com/nbQA-dev/nbQA/blob/main/LICENSE)
- [nbQA-dev/nbQA on GitHub](https://github.com/nbQA-dev/nbQA)
- [Project website](https://nbqa.readthedocs.io/en/latest/index.html)
- [README](https://github.com/nbQA-dev/nbQA/blob/main/README.md)
- [Releases](https://github.com/nbQA-dev/nbQA/releases)

---

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