# What Is an .ipynb File? — Notebook Structure and What Survives Export

Canonical: https://commareports.com/glossary/ipynb
Published: 2026-09-11

> An .ipynb file is a JSON document of cells and their stored outputs. Why it diffs badly, why GitHub sometimes won't render it, and exactly what is lost when you export to HTML.

# .ipynb

**An `.ipynb` file is a Jupyter notebook: a JSON document containing an
ordered list of cells, each with its source and, for code cells, the
outputs captured the last time it ran.**

```json
{
  "cells": [
    { "cell_type": "markdown", "source": ["# Retention analysis\n"] },
    {
      "cell_type": "code",
      "execution_count": 7,
      "source": ["df.groupby('cohort').retention.mean().plot()\n"],
      "outputs": [
        {
          "output_type": "display_data",
          "data": { "image/png": "iVBORw0KGgo…", "text/plain": ["<Axes: >"] }
        }
      ]
    }
  ],
  "metadata": { "kernelspec": { "name": "python3" } },
  "nbformat": 4,
  "nbformat_minor": 5
}
```

## Outputs live in the file, and that is the whole problem

Storing outputs is what makes a notebook self-describing — you can read
someone's analysis without running it. It is also why:

- **Diffs are unreadable.** A re-run changes execution counts and
  re-encodes every image, so a one-character edit shows as thousands of
  changed lines. `nbstripout` or `jupytext` in a pre-commit hook is the
  standard defence.
- **Files get big fast.** Base64 images inflate by
  [a third](/glossary/data-uri). Multi-megabyte notebooks are ordinary,
  and GitHub's inline renderer gives up on them.
- **Merge conflicts corrupt them.** A conflicted JSON document is not a
  notebook, and the error a reader sees is a parse failure, not a merge
  marker.

## What survives an HTML export

`jupyter nbconvert --to html notebook.ipynb` produces a page with no
kernel behind it.

| Survives                                        | Does not survive                |
| ----------------------------------------------- | ------------------------------- |
| Markdown, code, text and table output           | `ipywidgets` sliders and inputs |
| matplotlib and seaborn images                   | `@interact` callbacks           |
| Plotly, Bokeh, Altair (they embed their own JS) | Anything recomputing on view    |
| Stack traces and stderr                         | `input()` prompts               |

The widget case is the frequent surprise: the export contains the widget
_state_ but not the Python that responds to it, so the control renders
and does nothing — or renders as a blank area
([why widgets don't render](/fix/jupyter-widgets-not-rendering-in-html)).

Interactive charts are the opposite story. Plotly and Bokeh ship their
interactivity as JavaScript inside the output, so zoom, hover and legend
toggles work in a published HTML export exactly as they did in the
notebook — provided the report is served somewhere that lets scripts run
([interactive HTML reports](/interactive-html-reports)).

## Sending the analysis, not the file

Sending the `.ipynb` requires the recipient to have Python, the
environment, and a reason to trust the file. Exporting to HTML and
publishing it gives them a URL that opens on a phone, keeps the charts
live, and can be commented on — so a stakeholder selects the number they
are querying and asks about it there, rather than screenshotting it into
a thread ([comment on HTML](/comment-on-html)).

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited
revision history.

**[Publish a notebook →](https://commareports.com/)**

### Related

- [Share a Jupyter notebook as HTML](/share-jupyter-notebook-html) · [nbconvert](/share-nbconvert-html) · [Colab](/share-colab-notebook)
- [Data URI](/glossary/data-uri) · [Self-contained HTML](/glossary/self-contained-html)
- [Data scientists](/for/data-scientists) · [Glossary](/glossary)
