nbdev 3: Notebook-Driven Development with a New pyproject.toml Core
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 3 moves configuration from settings.ini to pyproject.toml, keeps two-way notebook-to-source sync, and generates docs, tests, and releases from Jupyter notebooks. This review covers the new migration path, the command set, and the trade-offs of adopting a notebook-first workflow.
- Who is it for?
- Adopt nbdev 3 if you live in Jupyter notebooks and want a single source for code, tests, and docs, and if you can accept the PEP 621 migration and the Unix-only environment. Do not adopt it if your team works primarily in an IDE, needs native Windows support, or cannot tolerate the notebook-as-source constraint.
- 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 2 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 14, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem nbdev 3 solves: keeping code, tests, and docs in one place
Most Python projects split code into .py files, tests into a separate directory, and docs into a third location. Keeping those in sync is manual work. nbdev 3 attacks that by making the Jupyter notebook the single source of truth. You write exported cells that become module code, other cells that become tests, and markdown cells that become documentation. The README states that you get "high-quality documentation, tests, continuous integration, and packaging for free". The target user is someone who already thinks in notebooks, likely a researcher or a data scientist who wants to ship a pip-installable package without learning the full packaging toolchain. The platform is opinionated: it enforces that tests and docs are first-class, not afterthoughts. That is a real workflow change, not just a packaging shortcut.
How the two-way sync works: cell IDs and nbdev-update
The core mechanism is a two-way sync between notebooks and plaintext source. Each exported cell is tagged with its unique notebook cell ID. When you edit the exported .py file in an IDE, nbdev-update propagates that change back to the originating notebook cell. The README claims the sync is "robust" because the cell ID guarantees the update lands in the correct cell, even if cells have been reordered. This is a different approach from tools that treat notebooks as a one-way export target. The practical consequence is that you can use your IDE for navigation or quick edits without abandoning the notebook as the canonical form. The sync also means that merge conflicts in notebooks are handled by nbdev-fix, which creates a working notebook from a conflicted one, and by git hooks that render conflicts in a human-readable format. That is a concrete mechanism, but the README does not detail what happens when a cell is deleted on one side and edited on the other. That edge case is worth testing before you trust the sync in a multi-branch team.
The breaking change in nbdev 3: settings.ini to pyproject.toml
The January 2026 update moved configuration from settings.ini to pyproject.toml, following PEP 621. This is a major version bump for a reason. Project metadata now lives in the standard [project] section, while nbdev-specific settings go in [tool.nbdev]. The migration command is nbdev-migrate-config, which converts settings.ini to pyproject.toml and updates GitHub Actions workflows to nbdev3-compatible versions. The README is explicit that existing notebooks and code do not need changes, but the config format absolutely does. For a project that has been on nbdev2 for years, this is a one-time cost. The upside is that your packaging metadata is now in a standard location that other tools understand. The downside is that any custom settings you had in settings.ini may not map cleanly. The README does not list which keys migrate automatically and which need manual attention. That is a gap you will discover only when you run the command.
Getting started: install, environment, and the nbdev-new scaffold
Installation is a single pip command: pip install nbdev. But there is a hard constraint: nbdev must be installed into the same Python environment that you use for both Jupyter and your project. That is not optional. If you use separate virtual environments for Jupyter and your package, nbdev will not see your notebook kernels or your project dependencies. The README also states that nbdev works on macOS, Linux, and most Unix-style systems, and on Windows only under WSL, not under cmd or Powershell. That rules out a large chunk of Windows-native developers. After install, you create a new project with nbdev-new, which scaffolds the directory structure and a pyproject.toml. The command list includes nbdev-create-config for generating a config file manually, and nbdev-install to install Quarto and the current library. Quarto is a hard dependency for docs generation, so you need it in your environment or CI. The full command set is visible via nbdev-help, which is a good way to discover what the tool can do without reading the docs.
The command set: from export to release in one terminal
nbdev ships a large set of console scripts. The core ones are nbdev-export, which converts notebooks to Python modules, and nbdev-test, which runs tests in parallel across notebooks. The release pipeline is a chain: nbdev-bump-version increments the version in __init__.py, nbdev-changelog creates a CHANGELOG.md from closed and labeled GitHub issues, nbdev-release-gh tags and creates a GitHub release, and nbdev-pypi builds and uploads to PyPI. There is also nbdev-conda for building a meta.yaml and optionally uploading to conda. The command nbdev-release-both handles both PyPI and conda in one call. This is a complete release workflow, but it is tightly coupled to GitHub and to the assumption that you use GitHub Issues with labels. If you use a different forge or a private issue tracker, nbdev-changelog will not work as intended. The README does not mention any fallback for that case. The command nbdev-requirements writes a requirements.txt from pyproject.toml, which is useful for non-pip workflows, but it is an extra step you have to remember.
A real limitation: the mixed import-and-computation rule
The FAQ contains a specific warning about cells that mix imports and computations. You cannot have a non-exported cell that contains both an import statement and other code. For example, a cell with import some_module followed by some_module.something() is rejected. The README says you must split that into two cells. The reason is that the docs generation checks that all function signatures are up to date, and a mixed cell makes that check unreliable. This is a genuine constraint on how you write notebooks. It is fine for clean code, but it is a friction point if you are used to exploratory cells that import and run in one go. The rule only applies to top-level statements; try: import blocks and imports inside function definitions are allowed. So the restriction is narrow, but it is still a rule you have to internalize. If your team writes messy notebooks, nbdev will force a discipline that may be unwelcome.
The alternative: plain Python packages with Sphinx or MkDocs
The obvious alternative is to skip notebooks entirely and write a normal Python package with .py files, a tests directory, and a docs tool like Sphinx or MkDocs. The difference in approach is fundamental. With nbdev, the notebook is the source of truth, and the .py files are generated artifacts. With a traditional setup, the .py files are the source, and notebooks are either absent or used only for examples. The trade-off is that nbdev gives you live objects in your development environment, which the README says makes debugging and refactoring easier, but it also ties your project structure to Jupyter. A traditional setup gives you full control over your package layout and lets you use any editor, but you have to maintain the docs and tests separately. For a team that already uses Sphinx, nbdev's Quarto-based docs may feel like a foreign system. The choice is not about which is better, but about whether you want the notebook to be the center of your workflow or just a tool for exploration.
Maintenance cost and license implications
nbdev is licensed under Apache-2.0, which is permissive for commercial use, but it does not come with any warranty or support. The project is actively maintained, with a release as recent as August 2026, but the January 2026 breaking change shows that major version upgrades can require config migration. The maintenance cost of adopting nbdev is ongoing: you have to keep Quarto installed and up to date, run nbdev-export and nbdev-test as part of your CI, and handle the git hooks that clean notebook metadata. The README mentions nbdev-install-hooks for setting up Jupyter and git hooks, and nbdev-clean for cleaning notebooks to avoid merge conflicts. These are extra moving parts compared to a plain Python project. On the license side, Apache-2.0 means you can use nbdev in a commercial product, but you must preserve the license notice and state changes if you redistribute it. That is standard, but it is a legal obligation you should be aware of. The documentation does not cover what happens if you stop using nbdev: your generated .py files remain valid, but your source of truth is still the notebooks, so you are locked into the notebook format unless you migrate manually.
Editorial conclusion
Adopt nbdev 3 if you live in Jupyter notebooks and want a single source for code, tests, and docs, and if you can accept the PEP 621 migration and the Unix-only environment. Do not adopt it if your team works primarily in an IDE, needs native Windows support, or cannot tolerate the notebook-as-source constraint. Before committing, verify that your existing notebooks contain no mixed import-and-code cells, run nbdev-migrate-config on a copy of your project, and confirm that Quarto installs cleanly in your CI environment. The real test is whether the two-way sync keeps your exported modules stable across a few release cycles.
Community notes