emjupy edits and runs Jupyter python notebooks in Emacs. It talks to a running Jupyter server over the same HTTP and WebSocket API the browser uses, so a kernel on another machine works through an ssh tunnel, and the notebook stays a standard .ipynb that any other Jupyter tool opens. Completion, documentation and M-. come through eglot, from a language server running beside the kernel.

It uses Emacs's own tools – eglot, xref, eldoc, tramp – with whatever completion setup you already have (vertico, corfu, orderless or anything else). See "Looks" tab for screenshots.

Interactive plots and widgets can be shown in an external window or browser.

Try it with emacs -Q

quickstart.gif
Figure 1: The steps below in a vanilla emacs -Q (Emacs 30.1).

Without touching your own Emacs configuration. The repository ships a small notebook, test_notebook/test.ipynb, with a module beside it, so there is something real to run. To test with your own notebooks, jump to step 4.

  1. Get emjupy.

    bash
    git clone https://github.com/mathren/emjupy
    cd emjupy
    
  2. A Python environment with Jupyter. Either create the one the notebook comes with,

    bash
    conda env create -f test_notebook/environment.yml
    conda activate test_emjupy
    

    or install into one you have:

    bash
    pip install jupyter-server ipykernel jupyter-lsp 'python-lsp-server[all]' matplotlib numpy tqdm
    

    jupyter-lsp and python-lsp-server are what give completion, documentation and M-.; without them everything else still works.

  3. Start a server in the notebook's directory, in its own terminal:

    bash
    cd test_notebook
    jupyter server --no-browser --port=8888 --IdentityProvider.token=abc
    

    The server can equally run on another machine – started the same way, in a copy of test_notebook/ or any directory with notebooks. Then forward its port from this one, and use the same port below:

    bash
    ssh -N -L 8888:localhost:8888 <remote machine>
    
  4. Start Emacs from the repository root:

    bash
    emacs -Q -l src/quicktry.el
    

    -Q leaves your own configuration out; quicktry.el puts emjupy on the load path and installs its one dependency, websocket.el, into a temporary package directory, so nothing in your setup is touched.

  5. Log in: M-x emjupy-login RET 8888 RET, and abc when asked for the token. Pick test.ipynb from the list.
  6. Try it:
    • C-c C-c runs the cell at point and moves to the next;
    • M-. on example_function jumps to its definition in library.py;
    • C-c C-v shows every command, grouped by what it is for.

Features

  • persistent kernel session (to load large dataset once and re-use them)
  • remote or local kernel (just needs port and token)
  • in-line rendering of output including images
  • adopts the kernel already running behind a port, and the Python environment it runs in
  • completion, documentation and M-. through eglot, from a language server running beside the kernel via jupyter-lsp
  • resilient to connection drops (remote kernel keeps running, reconnect Emacs)
  • toggle hide/show output without re-running cells
  • interactive plotly figures open in a window of their own; ipywidgets controls work in the buffer, and widgets that draw themselves open in a live page
  • every cell command – insert, delete, move, split, join – is undone and redone like any edit
  • ein-like workflow and key bindings
  • emjupy-notebook-list, like ein:notebooklist, to browse notebooks and the files beside them

Installation

Clone this repo, add its src/ directory to load-path, and load it with these lines in your init.el or similar:

elisp
(add-to-list 'load-path "/path/to/emjupy/src")
(require 'emjupy)

websocket.el must be installed too, from GNU ELPA or MELPA.

Or with use-package (remove the :rev :newest line to use the latest release rather than the latest commit):

elisp
(use-package emjupy
  :vc (:url "https://github.com/mathren/emjupy"
       :lisp-dir "src"
       :rev :newest)
  :config
  (setq emjupy-render-latex t))

:lisp-dir "src" is needed: the sources are in src/, and without it the package installs but emjupy cannot be loaded.

Alternatively, M-x package-install-file on the tarball make package builds.

Pending on MELPA.

Landscape of other Emacs and Jupyter packages

Feature emjupy ein emacs-jupyter ob-jupyter
Notebook-style buffer of cells ✓ ✓ ✗ ✗
Reads and writes standard .ipynb ✓ ✓ ✗ ✗
Notebook list UI ✓ ✓ ✗ ✗
Undo across cell operations ✓ ✗ – ✓
Interactive widgets ✓ ✗ ~ (in a browser) ?
Kernel via Jupyter server HTTP + WebSocket (ssh tunnel is enough) ✓ ✓ ✓ ✓
No emacs-zmq dynamic module required ✓ ✓ ✗ ✗
eglot (completion, M-., rename across cells) ✓ ✗ ✗ ✗
Kernels other than Python ✗ ✓ ✓ ✓
Oldest Emacs supported 30.1 26.1 27 27
Maintained ✓ ✗ ✓ ✓

✓ = supported, ✗ = not supported, ~ = partly, – = does not apply (a REPL has no cell operations), ? = not documented. Checked 2026-10-03 Sat against each project's README and package header.

The packages differ mainly in what they edit and how they reach the kernel. ein and emjupy edit .ipynb notebooks in a buffer of cells over the HTTP and WebSocket API of a Jupyter server, which is why an ssh tunnel is enough. emacs-jupyter is a kernel client built around a REPL and Org-babel source blocks, so it has no notebook buffer and does not read .ipynb. It requires the emacs-zmq dynamic module, whose ZMQ transport cannot reach the kernels behind a notebook server, although it can also talk to a server's REST API through TRAMP-style /jpy:host#port:name names; its widget support is experimental, through an external browser. In exchange for notebook-level eglot integration, emjupy is Python only (other kernels untested), needs jupyter-lsp on the server, and requires Emacs 30.1+.

ein was my working tool for more than a decade, but it is unmaintained – its README has called it sunset since 2023, and its architecture "fundamentally incompatible with LSP". That README now points to xjupyter, a greenfield successor at version 0.0.1, which runs local kernels through a Python jupyter_client helper rather than a Jupyter server, and needs mode-overlay, which GNU Emacs 30.1 does not have. I need to share code with non-emacs users, so ob-jupyter isn't an option, and emacs-jupyter doesn't seem the right tool for me.

Rest of these docs