# molten-nvim: running code from a Neovim buffer through a Jupyter kernel

> Molten is a fork of magma-nvim that sends code from Neovim buffers to a Jupyter kernel and shows the output inline, as a floating window or as virtual text. It is a remote plugin with real requirements, Neovim 9.4 and Python 3.10, and it is still on the 1.9 line with the last tagged release from January 2025.

**benlubas/molten-nvim** — A neovim plugin for interactively running code with the jupyter kernel. Fork of magma-nvim with improvements in image rendering, performance, and more

- Repository: https://github.com/benlubas/molten-nvim
- Stars: 1,240 · Forks: 72
- Language: Python
- License: GPL-3.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/benlubas-molten-nvim

## A magma fork aimed at output fidelity and speed

Molten is a fork of Magma, the plugin that established the idea of running code from a Neovim buffer through a Jupyter kernel. The fork's stated difference is in image rendering, performance, and further improvements, and the README says Molten provides a repl-like experience and a notebook-like experience inside Neovim.

The problem it solves is specific to people who live in an editor. If you write Python, you have probably had a moment where you moved a function into a cell in a `.ipynb` file just to get a plot to render or a value printed, then moved it back. A Jupyter kernel connected to your buffer removes that shuttle. Code is sent to a kernel asynchronously, the result appears under the code, and you stay in the editor.

The audience is therefore Neovim users on scientific or data work who already have a kernel on the other side, usually Python. Molten does not provide the kernel, and it does not manage environments for you beyond pointing at one. It provides the bridge.

The fork relationship matters for how you read the documentation. The differences live in the changelog rather than in a fresh design. The repository carries a `CHANGELOG.md`, and it is the file to read before migrating, because a fork that renames or extends a command can silently change the behaviour a muscle memory depends on.

## A remote plugin, and what that means for where Python runs

The README opens with a warning that is worth taking literally. Molten is a remote plugin, and the project links to a section in its own guide explaining what that means for people who do not know the term.

A remote plugin in Neovim is a plugin whose implementation lives in another process and talks to the editor over the plugin host API. Molten's Python side is not executed inside Neovim. It runs as a separate Python process, and the Lua side in `lua/` drives it, with the Python source living under `rplugin/`. The `pyproject.toml` confirms the split, pointing pyright at `rplugin/python3` as an extra path, and the repository also carries a `flake.nix` and `flake.lock` for a Nix based setup.

The practical consequences are direct. You need a Python 3.10 or newer interpreter available to the plugin host, and the required packages `pynvim` for the Remote Plugin API and `jupyter_client` for talking to Jupyter have to be installed in the environment that Python actually uses. The README points to a separate guide on virtual environments for exactly this reason, and notes that optional packages are only imported when the feature that needs them is used, so a missing `pnglatex` does not break your basic workflow.

The other consequence is process boundary debugging. When something does not work, the question of which Python is running and where its packages live comes up immediately. The project answers that question with `:checkhealth`, which reports what it found installed. Running it should be the first step, not the last.

Neovim 9.4 or newer is required, along with Python 3.10 or newer. Both floors are stated plainly, and both are reasonable for a plugin that relies on the modern remote plugin API.

## Cells are extmarks, not markdown blocks

Molten's central abstraction is the cell, and the way it defines one is the most interesting design decision in the plugin.

Executing code creates a cell, and you can recognise a cell because it is highlighted when your cursor is inside it. The cell is delimited using two extmarks, the extended marks feature described in `:h api-extended-marks`, which means the cell boundaries adjust as you edit the text inside them. The cell moves with the code instead of being pinned to a line range.

The important caveat is stated immediately afterwards, and it is a deliberate decision rather than a missing feature. A molten cell is not a markdown cell. Molten has no awareness of the type of file it is running code from, and does not need to know. So the plugin can run a code block in a plain `.py` file, a `.R` file or a `.jl` file with the same mechanics, and it will not try to render markdown the way a notebook viewer does.

The corollary is that Molten cells and notebook cells are different objects with different lifecycles. If you import a notebook file, the cells you get are Molten's cells, not a live link back to the notebook's structure. The README notes that import and export to and from Jupyter notebook files is supported, with the explicit caveat that it does not convert an `.ipynb` file to plaintext, and that `nbformat` is the optional package required for it.

Overlapping cells are not allowed. If you create a cell that overlaps an existing one, the old cell is deleted. That is a reasonable rule, since extmark ranges have to be disjoint to be meaningful, but it does mean that re-running over a region that spans an existing cell can discard the earlier cell's output.

## Where output appears: a floating window and virtual text

Output has two homes, and the plugin supports both at once.

The first is the floating output window, shown below the cell when your cursor is in it. When your cursor leaves a cell, that floating window closes. Whether a window is shown at all is controlled by `g:molten_auto_open_output` and can be inspected with `:MoltenShowOutput`.

The second is virtual text below the cell. That output stays in place until you re-run the cell or delete it, which makes it the persistent option. The README stresses that output appears in real time and without flicker, which is the specific complaint the virtual text and floating window split is meant to solve: a scrolling region that redraws on every flush makes a plot unreadable.

The floating window has a header that reports the execution count and the execution state, meaning whether the cell is waiting to run, running, finished successfully or finished with an error. That distinction between a running cell and a failed cell is carried in the header rather than inferred from the output text, so a cell that produces no output still tells you what happened to it.

Not every Jupyter output is supported, and the README is honest that Jupyter provides a rich set of outputs while a subset is currently handled. The section on output chunks is the reference for what the plugin can render, and it is worth reading before you assume a particular MIME type will display.

## Command set and the kernel resolution rule

The commands are the main interface, and the README recommends mapping most of them to keys.

`MoltenInfo` is the diagnostic one, showing the state of the plugin, the initialization status, the available kernels and the running kernels. `MoltenInit` initializes a kernel for the current buffer, and takes an optional `shared` argument, in which case the buffer attaches to a kernel that is already running rather than starting its own, plus an optional kernel name that prompts the user if it is omitted. `MoltenDeinit` tears down the buffer's runtime and molten instance, and the README says it is called automatically when the buffer closes or unloads.

The rest are navigation and evaluation. `MoltenGoto` jumps to the nth code cell, `MoltenNext` and `MoltenPrev` move by a number of cells with wrapping, and negative values move backwards. `MoltenEvaluateLine` evaluates the current line and `MoltenEvaluateVisual` evaluates the visual selection, and the latter cannot be called with a range.

The kernel resolution rule is documented once and applies to every command that takes an optional kernel argument. If a kernel is given, the code goes there. If not, and the buffer has exactly one active kernel, that one is used. If the buffer has more than one, the user is prompted. A related behaviour is configurable through `molten_auto_init_behavior`, which controls whether some commands prompt for a kernel when the buffer has none attached.

Two directions of sharing are supported and they are not the same thing. Code from multiple buffers can be sent to the same kernel, and code from the same buffer can be sent to different kernels. That combination is what makes a multi-notebook workflow possible in one editor, and it is also where confusion comes from, since the kernel is per buffer and the buffer is per file.

## Image rendering depends on a provider you install separately

Rendering images, plots and LaTeX inside Neovim is the feature the fork is named for improving, and it is where the optional dependencies pile up.

There are two image providers, and each needs a different plugin. The `image.nvim` provider requires 3rd/image.nvim, and the `wezterm` provider requires wezterm.nvim. Plots and LaTeX follow the same pattern on the Python side: `pnglatex` displays TeX formulas, and `plotly` with `kaleido` displays Plotly figures. SVG transparency needs `cairosvg`, although the README notes that image.nvim renders SVG images fine without it when transparency is not needed. `pillow` is needed for `:MoltenImagePopup`, and `pyperclip` for `molten_copy_output`.

A separate optional route exists for connecting to a Jupyter Server over the network. With `requests` and `websocket-client` installed, `:MoltenInit <Jupyter server URL>` connects to a Jupyter Server API over HTTP and WebSocket, so the kernel does not have to be local to the machine running Neovim. That is a useful arrangement when the expensive machine is elsewhere, and it also means the plugin is talking to a remote service with your credentials, which is worth keeping in mind.

The general rule the README states is that optional Python packages are only imported when they are used, so the dependency surface grows as you use more of the plugin rather than all at once. The cost of that design is that a missing package shows up as a failed feature rather than as a failed health check, and `:checkhealth` is what tells you what is actually present.

## Version 1.9, GPLv3, and where a Jupyter kernel is the wrong tool

The maintenance picture is the thing to weigh before you adopt this. The recent tags are v1.9.2 from 2025-01-28, v1.9.1 from 2024-12-26 and v1.9.0 from 2024-10-12. The last push to the default branch was on 2026-07-03, and the repository is not archived.

So the code is being worked on, but the tagged line has not moved past 1.9.2 in a long time. That combination usually means development happens on `main` and releases lag, or that the maintainer is shipping fixes without cutting a tag. Either way, if you are a plugin manager user, the version you receive depends on whether your manager tracks the branch or the tag, and the answer differs.

The licence is GPL-3.0. For a plugin you install into your own editor configuration, that is not a practical constraint. If you were to redistribute a modified Molten inside a product or a shared configuration bundle, the copyleft obligations of the GPL apply, and that is a question for your own review rather than for this article.

On fitness for purpose, there is a clear case where Molten is the wrong tool. If your code is a script with a linear flow, `nvim --headless` plus a task runner is simpler, has no kernel, and no Python environment to keep in sync. Molten earns its cost when you genuinely need a stateful kernel: a dataframe you keep mutating, a plot you refine iteratively, a REPL session with imported libraries already loaded. Outside that case you are paying for a remote plugin, a Python process and a kernel lifecycle in exchange for features you do not use.

There is a second, quieter case. The kernel state is shared state. A cell that mutates a variable changes what the next cell sees, and the same kernel can be attached to several buffers. That is the whole point of a REPL, and it is also the source of the confusion that makes a notebook feel unpredictable. Molten has commands and a status window for managing that state, but it does not remove it.

## Conclusion

Adopt molten-nvim if you work in Neovim, write Python against a Jupyter kernel, and want notebook style execution without leaving the editor, especially if you already depend on image.nvim or a wezterm image provider for plots. Do not adopt it as a drop-in for a plain script workflow, since it is a remote plugin with its own Python environment and a Neovim 9.4 floor, and it does not know anything about markdown cells. Verify three things first: that `:checkhealth` reports a working Python environment with `pynvim` and `jupyter_client`, that the kernel you attach to actually has the packages your code imports, and that the image provider you plan to use is installed, since rendering a plot silently depends on `image.nvim` or `wezterm.nvim` being present.

## FAQ

### What is molten-nvim and what does it do?

Molten is a Neovim plugin, forked from magma-nvim, that sends code from a buffer to a Jupyter kernel and shows the output inline. It requires Neovim 9.4 or newer and Python 3.10 or newer, and the Python side runs as a remote plugin.

### How do I install molten-nvim?

The README does not give an install command, it points to the quick start guides under `docs/`, including a Probably Too Quick Start Guide, a Not So Quick Start Guide, plus separate pages for Windows and NixOS users. You also need the Python packages `pynvim` and `jupyter_client`, and `:checkhealth` shows what is present.

### How do I run a line of code in molten-nvim?

Use `:MoltenEvaluateLine` for the current line and `:MoltenEvaluateVisual` for the visual selection. A kernel has to be initialized first with `:MoltenInit`, which can take a `shared` argument to attach to a kernel that is already running.

### Do molten-nvim cells work in a plain Python file?

Yes. A molten cell is not a markdown cell, and Molten has no awareness of the type of file it is running code from. Cells are delimited with extmarks so their boundaries shift as you edit the text inside them.

### Why are my plots not showing in molten-nvim?

Image rendering needs a provider, either 3rd/image.nvim for the `image.nvim` provider or wezterm.nvim for the `wezterm` provider. On the Python side, `plotly` and `kaleido` are needed for Plotly figures, `pnglatex` for TeX formulas and `cairosvg` for SVG transparency.

### Can molten-nvim connect to a remote Jupyter server?

Yes, with the optional `requests` and `websocket-client` packages installed, `:MoltenInit <Jupyter server URL>` connects to the Jupyter Server API over HTTP and WebSocket. The kernel then runs on that server rather than on the machine running Neovim.

## Sources

- [benlubas/molten-nvim on GitHub](https://github.com/benlubas/molten-nvim)
- [Issues](https://github.com/benlubas/molten-nvim/issues)
- [License: GPL-3.0](https://github.com/benlubas/molten-nvim/blob/main/LICENSE)
- [README](https://github.com/benlubas/molten-nvim/blob/main/README.md)
- [Releases](https://github.com/benlubas/molten-nvim/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/benlubas-molten-nvim
