nbdev: notebook-driven development from Jupyter to PyPI
Create delightful software with Jupyter Notebooks. Docs support LaTeX, are searchable, and are automatically hyperlinked (including out-of-the-box support for many packages via nbdev-index) Publish packages to PyPI and conda as well as tools to simplify package releases.
At a glance
- What is it?
- nbdev turns Jupyter notebooks into the source of truth for a Python library: exports, tests, Quarto docs and PyPI releases all come from the same cells. Version 3 moved configuration to pyproject.toml, and that migration is the first thing to plan for.
- Who is it for?
- Adopt nbdev if your team already writes Python in notebooks and wants tests, docs and packaging generated from the same cells, and budget time for the nbdev3 configuration move before writing new code. Do not adopt it if you need Windows support outside WSL, or if your project is large enough that running nbdev-prepare on every change is a cost you cannot absorb.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Jupyter Notebook, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem nbdev solves: notebooks that never become packages
A notebook is a good place to explore and a bad place to ship from. Cells drift out of order, imports get buried next to computations, and the only artifact anyone can install is whatever a human copied into a .py file months later. nbdev's answer is to keep the notebook as the source of truth and make everything downstream a generated artifact. The README describes it as a notebook-driven development platform where you "simply write notebooks with lightweight markup and get high-quality documentation, tests, continuous integration, and packaging for free."
The audience is narrower than "Python developers." It is people who already think in notebooks and want library-grade output: a package on PyPI, a Quarto documentation site on GitHub Pages, and a test suite that runs in parallel. If you write Python in an editor and treat notebooks as scratch space, the workflow inverts your habits rather than fitting them.
One design decision worth naming: only exported objects end up in __all__. That is a real constraint, not a convenience. Anything you want importable has to be marked for export in the notebook, which means the notebook cell carries the decision about the public API.
How export and two-way sync actually work
The core mechanism is a pairing between notebook cells and Python modules. Cells marked for export are written out to source files, and each exported cell is tagged with its unique notebook cell ID. The README states that because of this tagging, nbdev-update "always updates the correct cell" when a change is propagated back from the module to the notebook. That is the answer to the usual objection about generated code: the round trip is keyed, not positional, so inserting a cell does not shift every later edit.
Documentation is generated by Quarto and hosted on GitHub Pages. Docs support LaTeX, are searchable, and are automatically hyperlinked, with out-of-the-box support for many packages through nbdev-index. Tests are ordinary notebook cells, run in parallel by a single command. Continuous integration is provided as GitHub Actions that run the tests and rebuild the docs.
The README also documents a warning you will hit early: "Found a cell containing mix of imports and computations. Please use separate cells." The stated reason is that documentation generation needs clean separation so function signatures can be rendered correctly. Only top-level statements trigger it; imports inside try blocks or function definitions are fine. This is a small rule that shapes how you write every notebook.
Installing nbdev and running the first commands
nbdev installs from PyPI. The README is explicit that it must go into the same Python environment you use for both Jupyter and your project, so install it where the kernel lives rather than in a separate tooling environment.
pip install nbdevPlatform support is stated plainly: macOS, Linux and most Unix-style systems work; Windows works under WSL but not under cmd or PowerShell. After installing, the README points to nbdev-help for the full list of console scripts rather than expecting you to memorise them.
nbdev-helpThat command prints the available entry points, including nb-export, nbdev-export, nbdev-test, nbdev-docs, nbdev-prepare, nbdev-pypi, nbdev-conda and nbdev-release-gh. From there the documented learning path is the written walkthrough at nbdev.fast.ai or the video walkthrough; the README does not present a minimal hello-world beyond those.
For an existing nbdev2 project, configuration moved from settings.ini to pyproject.toml. Project metadata now lives in the standard [project] section and nbdev-specific settings in [tool.nbdev]. The README gives the migration command:
nbdev-migrate-configRun it in the project root. According to the README it converts settings.ini to pyproject.toml and updates your GitHub Actions workflows to nbdev3-compatible versions, and it states that existing notebooks and code need no changes. A new project instead starts with nbdev-create-config, which the README describes as creating a pyproject.toml config file.
Where nbdev gets in the way
The Windows limitation is the clearest boundary. cmd and PowerShell are not supported; WSL is the documented path. Teams on Windows machines without WSL, or with policies that make WSL awkward, should treat this as a blocker rather than an inconvenience.
The import-and-computation rule is a second constraint. It is enforced by a warning rather than a hard failure, but the README frames it as something to avoid, and the reason given is documentation rendering. You are writing notebooks in a style that keeps cells small and single-purpose. That is good discipline and a real tax on exploratory work, where mixing an import with a quick call is the normal move.
The toolchain is also wide. A project depends on Quarto for docs, GitHub Actions for CI, and the nbdev console scripts for export, test, clean and release. Each of those is a component that can fail independently, and the README does not document rollback for a release that goes wrong. If you want a single command that turns a folder of .py files into a package, this is the wrong tool; nbdev assumes the notebook is where the code lives.
Finally, nbdev3 is a breaking change. The README marks the January 2026 update as such. The migration command is provided, but any project pinned to nbdev2 workflows has a real upgrade step in front of it.
nbdev compared with using Jupyter and setuptools directly
The obvious alternative is the manual route: keep notebooks for exploration, copy finished code into .py modules, and wire up setuptools, pytest and a docs generator yourself. The difference is where the source of truth sits. In the manual route the .py file is authoritative and the notebook is disposable; in nbdev the notebook is authoritative and the .py file is generated. That single inversion is what produces the rest of the behaviour: tests are cells rather than a separate tests/ directory, docs are built from the same cells, and the export step is what creates the module.
The cost of the manual route is duplication. Every change happens twice, and the notebook and module drift. The cost of nbdev is the constraint set: export markers, the import rule, and a build pipeline between you and a working import. Neither is free, and the choice depends on whether your team's real work happens in notebooks. If it does, the manual route loses time to copying. If it does not, nbdev adds a layer you will fight.
There is also the git question. Notebooks are JSON, so diffs are noisy and merges are painful. nbdev ships Jupyter and git hooks through nbdev-install-hooks that clean unwanted metadata and render merge conflicts in a human-readable format, plus nbdev-fix for repairing a conflicted notebook. A manual workflow has to solve that separately or live with it.
Release, licence and the cost of staying current
Releases are covered by dedicated commands. nbdev-pypi creates and uploads the Python package to PyPI, nbdev-conda creates a meta.yaml ready to be built into a package and can build and upload it, and nbdev-release-both handles both. nbdev-release-gh calls nbdev-changelog, lets you edit the result, then pushes to git and calls nbdev-release-git, which tags and creates a GitHub release for the current version. nbdev-changelog builds CHANGELOG.md from closed and labeled GitHub issues, so the quality of your changelog depends on how consistently you label issues.
Upgrade cost is the thing to weigh. The repository's last push was on 2026-08-25, and the recent releases listed are 3.3.13, 3.3.12 and 3.3.11 across August 2026. That cadence means patch upgrades arrive often, and the 3.x line is where the pyproject.toml configuration lives. Any project still on settings.ini is carrying a migration it has not done.
The project is Apache-2.0, both in the repository and in the pyproject.toml license field, with the classifier "License :: OSI Approved :: Apache Software License." Apache-2.0 is permissive and includes an explicit patent grant, which matters if you redistribute. It also means you are responsible for your own dependency licences: the runtime dependencies listed in pyproject.toml include fastcore, execnb, ghapi, fastgit, watchdog and others, each with its own terms. That is a compliance question for your organisation, not something the licence text resolves.
Editorial conclusion
Adopt nbdev if your team already writes Python in notebooks and wants tests, docs and packaging generated from the same cells, and budget time for the nbdev3 configuration move before writing new code. Do not adopt it if you need Windows support outside WSL, or if your project is large enough that running nbdev-prepare on every change is a cost you cannot absorb. Before committing, run nbdev-migrate-config in a scratch copy of your project and confirm the resulting pyproject.toml keeps every key your existing settings.ini carried, then run nbdev-help to check which console scripts your installed version actually provides.
Frequently asked questions
Is Jupyter Notebook outdated?
The README does not make a claim about Jupyter's age or status. It positions nbdev as a notebook-driven development platform that takes notebooks as the source of truth and generates docs, tests and packaging from them.
What is Jupyter used for?
The README does not define Jupyter's general purpose. In nbdev, notebooks are where you write the code, with lightweight markup, and the exported cells become Python modules while the same cells supply tests and Quarto documentation.
Which is better, Jupyter Notebook or JupyterLab?
The README does not compare the two interfaces. It only notes that nbdev must be installed into the same Python environment you use for both Jupyter and your project, so the kernel you run matters more than which front end you open.
Is Jupyter or VSCode better?
The README does not compare them. It does describe two-way sync between notebooks and plaintext source code, which the README says allows you to use your IDE for code navigation or quick edits.
Does nbdev work with VSCode?
The README does not document a VSCode integration. The closest thing it offers is two-way sync between notebooks and plaintext source code so you can use your IDE for code navigation or quick edits.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/answerdotai-nbdev)