Clean, reviewable diffs
No more unreadable JSON blobs. Text notebooks produce line-by-line diffs you can actually review in a pull request.
Jupytext saves your notebooks as .py or .md files — easy to edit
in any IDE, friendly to version control, and trivial for AI assistants to read and refactor.
# %% [markdown]
# # Sales report
- df = df.dropna()
+ df = df.dropna().reset_index(drop=True)
+ df["margin"] = df.revenue - df.cost
# %%
df.groupby("region").margin.sum().plot.bar() The same notebook — as a diff your team can actually read.
A one-line code change in a .ipynb notebook comes with execution counts, base64 image outputs, and cell metadata in the diff. Jupytext gives you a text file that contains only the source — the part you actually write and review.
notebook.ipynb- "execution_count": 12,
- "outputs": [{"data": {"image/png":
- "iVBORw0KGgoAAAANSUhEUg..."}}],
- "metadata": {"id": "a1b2c3"},
- "source": ["df = df.dropna()"]
+ "execution_count": 47,
+ "outputs": [{"data": {"image/png":
+ "iVBORw0KGgoAAAANSUhEUg..."}}], notebook.py # %%
- df = df.dropna()
+ df = df.dropna().reset_index(drop=True)
Keep the interactive notebook you love. Gain everything that plain text gives you.
No more unreadable JSON blobs. Text notebooks produce line-by-line diffs you can actually review in a pull request.
Open your notebooks in VS Code, PyCharm, Spyder, vim — anywhere. No JSON, no merge nightmares, full editor superpowers.
Run black, ruff, isort or flake8 over your notebooks like ordinary scripts. Pipe them through any tool from the CLI.
A text notebook is a valid module. Import functions from it, or run it as a script. Copy, move, and restructure your code at will.
Pair the text file with an .ipynb and keep your rich outputs. Inputs come from the text, outputs from the notebook.
Python, R, Julia and more — as percent scripts, Markdown, MyST, Quarto or Pandoc. Pick what fits each notebook.
Coding assistants like Claude, GitHub Copilot and Cursor work in plain text. Pair your notebooks with Jupytext and they can read, edit and refactor them directly — no JSON wrangling, no broken outputs, no special tooling.
# %% [markdown]
# ### Refactor: vectorize the loop 🤖
- totals = []
- for r in regions:
- totals.append(df[df.region==r].sales.sum())
+ totals = df.groupby("region").sales.sum()
Keep a rich .ipynb (with outputs) alongside a clean .py or
.md.
Jupytext keeps them in sync — commit only the text file.
Tell Jupytext to pair your notebook with a text format — once, or for the whole project via jupytext.toml.
Edit the .py in your IDE or with an AI assistant, or run the notebook in Jupyter. Reload to pick up changes.
On save (or with jupytext --sync) both files update. Version-control the text; outputs stay in the .ipynb.
jupytext --to py:percent notebook.ipynb jupytext --set-formats ipynb,py:percent notebook.ipynb jupytext --sync notebook.ipynb jupytext --pipe black notebook.ipynb # jupytext.toml
formats = "ipynb,py:percent" The cell below is the same content in each format. Pick whichever suits your needs.
# %% [markdown]
# # Analysis
# A quick look at the data.
# %%
import pandas as pd
df = pd.read_csv("data.csv")
df.describe() The most portable format — cell markers understood by VS Code, PyCharm, Spyder, etc., across Python, R, Julia and more.
# # Analysis
# A quick look at the data.
import pandas as pd
df = pd.read_csv("data.csv")
df.describe() Best when you want a script that barely looks like a notebook — minimal cell markers.
import marimo
__generated_with = "0.23.8"
app = marimo.App()
@app.cell
def _():
import marimo as mo
return (mo,)
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
# Analysis
A quick look at the data.
""")
return
@app.cell
def _():
import pandas as pd
df = pd.read_csv("data.csv")
df.describe()
return
if __name__ == "__main__":
app.run() Each cell is a function in the Marimo format
# Analysis
A quick look at the data.
```python
import pandas as pd
df = pd.read_csv("data.csv")
df.describe()
``` Best for documentation-first notebooks, GitHub Markdown syntax.
# Analysis
A quick look at the data.
```{code-cell} ipython3
import pandas as pd
df = pd.read_csv("data.csv")
df.describe()
``` Best for rich technical docs & Jupyter Book — directives, cross-refs and more.
---
title: Analysis
---
A quick look at the data.
```{python}
import pandas as pd
df = pd.read_csv("data.csv")
df.describe()
``` For multi-language scientific publishing — Python, R, Julia and more, rendered by the Quarto system.
---
jupyter:
nbformat: 4
nbformat_minor: 5
---
::: {#7e0ae658 .cell .markdown}
# Analysis
A quick look at the data.
:::
::: {#756f3401 .cell .code}
``` python
import pandas as pd
df = pd.read_csv("data.csv")
df.describe()
```
::: Pandoc's own Markdown format for notebooks — cells as fenced divs, readable as plain Markdown.
Install Jupytext, pair a notebook, and enjoy reviewable diffs, IDE editing and AI-ready files.
Jupytext was created by Marc Wouts. Follow Marc on LinkedIn for updates on Jupytext, his other open-source projects, and the occasional random topic.
Jupytext is completely free and open source under the MIT license. What keeps us going is
the excitement of learning through interactions with users and experts, and the satisfaction of sharing solutions with the community.
Have a question, a bug report, or an idea? Feel free to open an issue or start a discussion on GitHub.