mrkeyoor.com_
Thu 06 Aug 10:55 UTC
PyPIDataupdated 06 Aug 2026

nbconvert

nbconvert turns a .ipynb file into something that is not a notebook: HTML, LaTeX, PDF, Markdown, reStructuredText, AsciiDoc, a Reveal.js slide deck, or a plain executable script. It is the machinery behind the Download As menu in Jupyter, and it is what runs when you export a notebook from the command line with jupyter nbconvert --to html report.ipynb. Internally the pipeline is three stages: preprocessors that mutate the notebook object (execute cells, clear outputs, drop cells with a given tag), an exporter that feeds the notebook through a Jinja template, and a writer that puts the result on disk along with any extracted images. Every stage is a traitlets-configurable class, so anything the CLI does you can also do from Python or override with your own subclass. It can also execute a notebook and save the results back, which is how most teams use it as a regression test that their notebooks still run.

Verdict

The standard and effectively unavoidable way to get anything out of a .ipynb file, and the execute-in-CI use case alone justifies it. Reach for a narrower tool when your need is narrow: nbclient or papermill for running notebooks, nbstripout for cleaning them, and a site generator when you are publishing more than one.

API stability4/5The exporter, preprocessor, and writer classes have been stable across the whole 7.x line, and the traitlets config names in your CI script keep working. The last real break was 6.0, which removed the implicit HTML default and retired the 5.x .tpl template format, and template authors still feel that one.
Docs3/5The usage page is thorough on CLI flags and the customizing and removing-cells pages have working examples. Against that, the install page still lists Python 3.9 to 3.12 as the supported range, several pages describe the classic notebook UI rather than JupyterLab, and there is no single reference for the traitlets each preprocessor exposes; people end up reading the source.
Maintenance3/5Pushed 3 August 2026 and security issues get handled quickly, with 7.17.1 shipping fixes for two CVEs in April 2026. But 563 open issues (597 counting PRs) on a volunteer team, and a twelve-month gap between the 7.16.6 and 7.17.0 releases, means anything that is not a security fix or a crash waits a long time.
Ecosystem5/5Around 15.8 million downloads a week and a dependency of JupyterLab, Voila, Jupyter Book, and most notebook-adjacent tooling, so it is already installed almost everywhere Python notebooks exist. Third-party exporters and JupyterLab themes plug into the same template system.

Use it if

  • You need a CI check that your notebooks still execute end to end, and you want the failure to be a nonzero exit code rather than someone noticing next quarter
  • You publish notebook output to people who do not use Jupyter: a nightly HTML report on internal storage, a Markdown page in a docs site, or a slide deck for a review meeting
  • You want notebooks out of your git diffs: converting to a script or clearing outputs in a pre-commit hook makes review possible again
  • You need programmatic control over what appears in the output: hide input cells for a stakeholder report, drop cells tagged as setup, or run a template you wrote
  • You already have the Jupyter stack installed, so nbconvert is almost free and the alternative is writing your own nbformat walker
Skip it if

Setup reality

pip install nbconvert is the easy part, and it is not small: beautifulsoup4, bleach, defusedxml, jinja2, jupyter-core, jupyterlab-pygments, markupsafe, mistune, nbclient, nbformat, packaging, pandocfilters, pygments, and traitlets all come along. Everything past HTML and Markdown needs software pip cannot install. LaTeX, reStructuredText, and AsciiDoc output go through pandoc, which you install from your OS package manager. --to pdf additionally needs a TeX distribution with XeLaTeX and the recommended font packages, which on Debian means texlive-xetex, texlive-fonts-recommended, and texlive-plain-generic. --to webpdf skips TeX by rendering HTML in headless Chromium, but needs pip install 'nbconvert[webpdf]' and then a browser download, with --allow-chromium-download to let playwright fetch one. The qtpdf and qtpng exporters need pyqtwebengine. Executing notebooks needs a kernel: nbconvert does not depend on ipykernel, so a fresh install will fail with a kernel-not-found error until you install it yourself. Two more sharp edges: --to is mandatory since 6.0 removed the HTML default, and the readthedocs install page still claims Python 3.9 to 3.12 while the package metadata allows anything from 3.9 up.

Patterns

Convert a notebook to a standalone HTML filehtml-export

jupyter nbconvert --to html report.ipynb

jupyter nbconvert --to html --template classic report.ipynb
jupyter nbconvert --to html --template lab --theme dark report.ipynb
jupyter nbconvert --to html --embed-images report.ipynb

--to is required; 6.0 removed the old implicit HTML default, so a bare jupyter nbconvert now errors. Without --embed-images, plots land in a report_files/ directory next to the HTML and the page breaks the moment you move one file without the other.

Run every notebook and fail the build if one breaksexecute-in-ci

jupyter nbconvert --to notebook --execute --inplace notebooks/*.ipynb

# keep going and record the traceback in the cell output instead of aborting
jupyter nbconvert --to notebook --execute --allow-errors nb.ipynb

# the default per-cell timeout is 30 seconds
jupyter nbconvert --to notebook --execute \
  --ExecutePreprocessor.timeout=600 \
  --ExecutePreprocessor.kernel_name=python3 nb.ipynb

Without --inplace the result is written to nb.nbconvert.ipynb rather than over the original. The 30 second default timeout is the usual cause of a CI failure that does not reproduce locally; set it to None or -1 to remove the limit entirely. nbconvert does not depend on ipykernel, so install it or every run dies on kernel lookup.

Extract the code as a runnable scriptnotebook-to-script

jupyter nbconvert --to script analysis.ipynb        # writes analysis.py
jupyter nbconvert --to script --stdout analysis.ipynb
jupyter nbconvert --to script model.ipynb --output-dir src/

The output extension follows the kernel, so a Julia notebook gives you a .jl file. IPython magics such as %matplotlib inline are emitted as get_ipython() calls, which means the script only runs under IPython unless you strip them first.

Strip outputs before committingclear-outputs

jupyter nbconvert --clear-output --inplace notebooks/*.ipynb

# same thing spelled out
jupyter nbconvert --to notebook --inplace \
  --ClearOutputPreprocessor.enabled=True notebook.ipynb

--clear-output is a shortcut flag that already implies notebook output and in-place writing. It removes outputs and execution counts but leaves cell metadata alone, so tags and slide settings survive. If this is all you want from nbconvert, nbstripout does it with far fewer dependencies.

Produce a report with no code visiblehide-code-for-reports

jupyter nbconvert --to html --no-input report.ipynb

# keep code, drop the In[ ] / Out[ ] prompts
jupyter nbconvert --to html --no-prompt report.ipynb

--no-input hides input cells and both prompt gutters, which is what you want for a stakeholder-facing page. It hides the code, it does not remove it from anywhere else, and markdown cells still render, so do not use it to conceal credentials that are written in a cell.

Drop cells by tag instead of by handremove-tagged-cells

jupyter nbconvert report.ipynb --to html \
  --TagRemovePreprocessor.enabled=True \
  --TagRemovePreprocessor.remove_cell_tags remove_cell \
  --TagRemovePreprocessor.remove_input_tags hide_code \
  --TagRemovePreprocessor.remove_all_outputs_tags hide_output

Tags live in each cell's metadata and are editable from the JupyterLab property inspector. TagRemovePreprocessor is registered but disabled by default, so enabled=True is not optional. This is the clean way to keep scratch and setup cells in the notebook but out of the published version.

Pick between the two PDF pathspdf-output

# route 1: LaTeX. needs pandoc + a TeX distribution with xelatex
jupyter nbconvert --to pdf thesis.ipynb --template report

# route 2: headless Chromium. needs pip install "nbconvert[webpdf]"
jupyter nbconvert --to webpdf --allow-chromium-download report.ipynb

# route 3: stop at .tex and run xelatex yourself
jupyter nbconvert --to latex thesis.ipynb

--to pdf gives typeset output and chokes on emoji, unusual fonts, and raw HTML in markdown cells. --to webpdf renders exactly what the HTML export looks like and handles anything a browser handles, at the cost of a Chromium download. Inside a container you may also need --disable-chromium-sandbox.

Export from Python instead of the CLIpython-api-export

from nbconvert import HTMLExporter
from nbconvert.writers import FilesWriter

exporter = HTMLExporter(template_name="classic")
body, resources = exporter.from_filename("report.ipynb")

FilesWriter(build_directory="out").write(body, resources, notebook_name="report")

from_filename returns a two-tuple: the rendered text and a resources dict holding extracted images and metadata. Writing body to a file yourself and ignoring resources is the usual reason images go missing from the output.

Execute a notebook with the Python APIexecute-from-python

import nbformat
from nbconvert.preprocessors import ExecutePreprocessor

with open("pipeline.ipynb") as f:
    nb = nbformat.read(f, as_version=4)

ep = ExecutePreprocessor(timeout=600, kernel_name="python3")
ep.preprocess(nb, {"metadata": {"path": "notebooks/"}})

with open("executed.ipynb", "w", encoding="utf-8") as f:
    nbformat.write(nb, f)

The path in the resources dict is the working directory the kernel runs in, so relative data paths inside the notebook resolve against it rather than against your script. preprocess mutates nb in place and returns it. A failing cell raises CellExecutionError unless you set allow_errors=True.

Render a notebook you did not writesanitize-untrusted-html

jupyter nbconvert --to html --sanitize-html untrusted.ipynb

Off by default. Without it, HTML and script tags inside markdown cells and cell outputs pass straight into your page, which is a stored XSS bug the moment you serve it. Sanitization is not a substitute for keeping nbconvert current: 7.17.1 was released specifically to fix two CVEs.

Convert many notebooks into one output directorybatch-and-output-dir

jupyter nbconvert --to html --output-dir build/ notebooks/*.ipynb

jupyter nbconvert --to markdown notebook.ipynb --stdout

cat notebook.ipynb | jupyter nbconvert --stdin --to html

Each notebook is converted independently with no shared state, so links between notebooks do not become links between outputs. Supporting files go to build/<name>_files/. --stdout only makes sense for single-file formats, since anything producing images has nowhere to put them.

Turn a notebook into a slide deckreveal-slides

jupyter nbconvert --to slides talk.ipynb

# serve locally so speaker notes and timers work
jupyter nbconvert --to slides talk.ipynb \
  --reveal-prefix reveal.js --post serve

Cells need a slide type in their metadata (Slide, Sub-Slide, Fragment, Skip, Notes), set from the slideshow section of the JupyterLab property inspector. Untagged cells all pile onto the first slide. By default the deck loads reveal.js from a CDN, so an offline machine renders it unstyled.

Alternatives

PackageRegistryPick it when
papermillPyPIYou want to run a notebook with different parameters on a schedule and keep each executed copy, rather than convert it to another format.
nbclientPyPIYou only need to execute a notebook programmatically; this is the library nbconvert itself uses, without the exporters and templates.
nbstripoutPyPIThe only thing you want is outputs removed before committing, via a git filter or a pre-commit hook.
jupyter-bookPyPIYou are publishing many notebooks as one site or book and need navigation, cross-references, and build caching.