Turning Jupyter Notebooks into Self-Contained HTML Reports with nbconvert
Learn how to use nbconvert to export Jupyter notebooks as portable HTML files, including when to execute code during conversion and how to handle common limitations.
05 Jun 2026, 10:43 UTC

The sharing problem
You have spent hours cleaning data, fitting a model, and building a few matplotlib charts in a Jupyter notebook. Now you need to send the results to a colleague, a manager, or a reviewer. Emailing the raw .ipynb file is often a dead end: the recipient may not have Jupyter installed, and even if they do, the notebook might not run because of missing dependencies or changed data paths. Screenshots of plots lose the surrounding narrative. What you need is a snapshot—a single file that preserves your code, explanatory text, tables, and figures exactly as they appear.
The useful takeaway: jupyter nbconvert --to html transforms a notebook into one self-contained HTML file. Figures are embedded as base64 data URIs, meaning the images live inside the HTML itself rather than in separate files. The report opens in any modern browser with no server, no Python installation, and no network connection required.
Execute or freeze: the first decision
Before exporting, decide whether nbconvert should run your notebook. By default, nbconvert executes every code cell from top to bottom before generating HTML. This guarantees that the outputs in the report reflect the current state of your code, which is essential for reproducibility. If you edited a cell but did not re-run it, the default behavior fixes that silently.
However, execution is not free. It takes time, and it runs arbitrary Python code. If you received a notebook from an untrusted source, executing it during conversion is a security risk. For a static snapshot of a notebook you have already run interactively, skip execution with --no-execute. This is faster and safer, but it trusts that the stored outputs are correct.
A practical rule: execute when the notebook is part of a pipeline or when you need proof that the code runs cleanly; skip execution when you are simply archiving a finished analysis.
A worked example
Assume you have a notebook named analysis.ipynb containing a markdown introduction, a pandas dataframe summary, and a matplotlib line chart. Run the following in your terminal or command prompt from the directory containing the notebook:
jupyter nbconvert --to html --execute analysis.ipynb
You need read permission for the notebook and write permission for the current directory. The command should create analysis.html alongside the original file. If execution fails—perhaps because a package is missing—nbconvert stops and reports the error rather than producing a partial report.
To freeze the existing outputs instead, run:
jupyter nbconvert --to html --no-execute analysis.ipynb
Recent versions of Jupyter and nbconvert support several HTML templates. The classic template mimics the older Notebook 6 interface, while the lab template resembles JupyterLab. You can select one explicitly:
jupyter nbconvert --to html --template classic analysis.ipynb
Because defaults shift between releases, verify the available templates in your environment with jupyter nbconvert --to html --help.
Using the Python API
For CI pipelines or batch scripts, call nbconvert programmatically. In a Python environment where nbconvert and nbformat are installed:
import nbformat
from nbconvert import HTMLExporter
nb = nbformat.read('analysis.ipynb', as_version=4)
html_exporter = HTMLExporter()
body, resources = html_exporter.from_notebook(nb)
with open('report.html', 'w', encoding='utf-8') as f:
f.write(body)
This script reads the notebook, converts it, and writes the HTML. Note that the API call above does not execute the notebook; it exports whatever outputs are already stored. If you need execution in a pipeline, invoke the CLI with --execute or use ExecutePreprocessor explicitly.
What to check and what breaks
After conversion, open the HTML file in a browser. Confirm that the markdown is formatted, tables are rendered, and every figure appears. Because images are embedded, you can move the file to another machine or attach it to an email without losing plots.
Two limitations deserve attention. First, interactive widgets—such as ipywidgets sliders or Plotly charts in certain modes—may not survive the export. The static HTML preserves the last rendered state, but the interactivity is gone. If your report depends on user interaction, HTML export is the wrong medium; consider a hosted notebook service or a dashboard framework instead. Second, notebooks with very large images produce very large HTML files. Base64 encoding increases size by roughly one third compared to the binary image, so a 3 MB PNG becomes about 4 MB of text inside the HTML.
Custom styling is another common gap. If your notebook relies on JupyterLab themes or extensions that inject CSS or JavaScript at runtime, those styles will be absent from the exported file unless you supply a custom template. For most standard reports, the built-in templates are sufficient.
An actionable checklist
- Decide execution: Use
--executefor reproducibility,--no-executefor speed and safety. - Choose a template: Start with the default; switch to
classicorlabif the layout matters. - Verify the output: Open the HTML in a browser and check that figures, tables, and markdown render correctly.
- Check file size: If the HTML is tens of megabytes, consider downsampling images before export.
- Test interactivity: If widgets are critical, validate whether they appear in the exported file before distributing it.
Exporting to HTML does not modify your original notebook, so you can run the command repeatedly as your analysis evolves. Keep the command in a Makefile or shell script, and your reporting step becomes as reproducible as the analysis itself.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.