The Problem
I leverage LLMs like Claude and Cursor when generating lecture materials. They accelerate my work and help improve the end product with better sample code, etc. But all of those tools still struggle when creating / editing Jupyter notebooks, which is my medium of choice for this work. They can do it, but it is often a slow, cumbersome, more error-prone process. They sometimes resort to writing Python scripts to generate JSON that the ipynb format is based on. It is rapidly improving but not ideal.
But they do know markdown really well, so I went looking for a md to ipynb converter, and found… Jupytext!
I’m now able to easily round-trip markdown to ipynb and back, iteratively collaborating with my LLM of choice (#claude-boi).
What is JupyText?
Jupytext is a sort of swiss army knife for Jupyter notebooks. It converts them to and from plain text formats like Markdown, Python scripts, and R Markdown. It’s bidirectional and structure-preserving - unlike nbconvert (which is a one-way trip), or Pandoc (which can convert notebooks but doesn’t support the MyST format or paired file syncing). Think of it as filling the gap between notebook-first tools (Jupyter) and text-first tools (Quarto, RMarkdown, your text editor).
Installation
For use as a CLI-only tool, uvx is all you need. No install required. Just prefix any of the Jupytext commands that follow with uvx and you are good to go. For example, uvx jupytext --to ipynb lecture.md.
My Round-Trip Workflow
You can start at any point in this process.
- Generate
lecture.md - Convert:
jupytext --to ipynb lecture.md - Edit/execute in Jupyter
- Convert back:
jupytext lecture.ipynb --to md:myst - Send updated
.mdto AI for refinements - Repeat
With Section 6 set up, step 4 is handled automatically.
The Format
It’s markdown with some bells and whistles.
Frontmatter (MyST format)
Include frontmatter in your md file to specify how Jupytext should handle conversion. Note that we’re using the myst format…
---
jupytext:
text_representation:
extension: .md
format_name: myst
---Why MyST1? It’s markdown-native and explicitly distinguishes executable code cells ({code-cell}) from markdown code blocks, preventing AI confusion. You can include other information here, like which IPYNB kernel to use, but I find the four lines above are sufficient.
Code Cells
Use fenced code blocks with the {code-cell} directive:
```{code-cell} ipython3
import pandas as pd
import numpy as np
df = pd.DataFrame({'x': [1, 2, 3]})
```Force Cell Boundaries
Use +++ on its own line to force cell boundaries. I prefer to do this before and after all headers to put them in their own cell. This is because JupyterLab’s heading collapse does not hide body text in the same cell as the heading, just cells below it.
My AI Prompt Template
When asking an AI to generate jupytext-ready content, I’ll include something like this to specify Jupytext related details.
Format requirements:
- Use MyST-flavored jupytext with this frontmatter:
---
jupytext:
text_representation:
extension: .md
format_name: myst
---
Differentiate code that should be executable (have a run button) from those that
are only documentation (python formatted code blocks in markdown cells) by adding
`{code-cell} ipython3` after the opening triple-backticks on any code block.
Also, please use +++ BEFORE and AFTER ALL headers to put each in a separate cell
Other formatting preferences (no bold in lists, blank lines before code blocks, etc.) are handled by a Claude Code rules file that auto-loads when editing markdown.
This gives me clean, immediately-convertible markdown.
Converting with Jupytext
Manual conversion is as easy as…
# Markdown to notebook (--update preserves existing cell outputs)
jupytext --to ipynb --update lecture.md
# Notebook back to markdown (for AI refinement)
jupytext --to md:myst lecture.ipynbSyncing MD and IPYNB
If you want to get fancy, do the following…
# Sync pair (auto-update both) - advanced
jupytext --set-formats md:myst,ipynb lecture.ipynbAfter that every time you save either file in Jupyter, both are generated. Perfect for the round-trip workflow.
For sync-on-save to work, Jupytext must be installed as a Jupyter server extension - not just as a CLI tool. Install it in the same environment as Jupyter itself. Pick your poison, pip, conda, or uv to update your venv accordingly.
In practice, I set up syncing on every lecture project now and rarely run manual conversions.
Resources
- Jupytext docs
- MyST format guide
- MyST Markdown docs (full publishing features)
Footnotes
MyST Markdown is a powerful publishing system. Similar to Quarto / RMarkdown, it supports cross-references, figure numbering, citations, admonitions (aka callouts), etc. Jupytext provides tools to connect those ecosystems. For example, you can edit Quarto (
.qmd) documents as notebooks in Jupyter, and pair.ipynbnotebooks with.qmdnotebooks. For lecture notebooks, you only need the basics shown here. But if you want more, MyST scales up.↩︎