Profile
Back to NewsBack
GitHub Trending 18 min
Reader Mode
zasper-io/zasper: High Performace IDE for Jupyter Notebooks

zasper-io/zasper: High Performace IDE for Jupyter Notebooks

Zasper

High Performance IDE for Jupyter notebooks.
Any Jupyter kernel, one static binary, a fraction of JupyterLab's resource use.

Latest release Build status conda-forge License: AGPL-3.0-only

Documentation · Downloads · Installation · Slack

!Zasper running a Jupyter notebook

Overview

Zasper is an IDE for Jupyter notebooks, designed from the ground up for concurrency and a small footprint. It implements Jupyter's wire protocol, so it runs any Jupyter kernel, and it reads and writes the .ipynb format directly, so notebooks move between Zasper and JupyterLab unchanged.

Zasper is a single static binary with no runtime dependencies. It serves its interface to your browser, on your own machine or on a server you share.

  • Notebooks on any Jupyter kernel, with Plotly, ipywidgets, HTML and Markdown with LaTeX
rendered inline. A save is byte-for-byte what Jupyter would have written.
  • Editor, terminal and version control in the same window as your notebooks.
  • Command palette that finds files and every command in one search.
  • Light and dark themes, and window zoom.
  • Self-hosting built in: every session is protected by an access token.
More screenshots

Editor

!Editor

Terminal

!Terminal

Launcher

!Launcher

Version control

!Version control

Command palette

!Command palette

Dark theme

!Dark theme

!Notebook in the dark theme

Contents

Performance

In benchmarks against JupyterLab, Zasper uses:

  • up to 5× less CPU,
  • up to 40× less memory,
with higher throughput and lower latency, and it stays responsive under very high load.

!Resource use, Zasper compared with JupyterLab

The methodology and full results are in the benchmark report.

Installation

Current release version: v2.0.0

| Platform | Support | | --- | --- | | macOS | Supported | | Linux | Supported | | Windows | Binaries are published and notebooks run, but terminals are not available yet and some kernel paths are less well exercised. For terminals and the best experience, use WSL. |

Requirements. Zasper runs notebooks on Jupyter kernels but does not install one: pip install ipykernel is enough to start (see Jupyter kernels). You also need a modern browser, since Zasper serves its interface locally rather than being a separate desktop application. Without a kernel, Zasper still starts and the Launcher tells you what to install.

Homebrew

brew tap zasper-io/tap
brew trust zasper-io/tap
brew install zasper-io/tap/zasper

Homebrew 6 loads nothing from a third-party tap until you trust it, which is what brew trust records. If you installed 0.x through Homebrew, run brew uninstall zasper first: from 1.0, Zasper is published as a cask rather than a formula.

Snap

sudo snap install zasper

Get it from the Snap Store

conda

conda install -c conda-forge zasper

Prebuilt binaries

Every release ships a signed, notarized macOS build and static binaries for Linux and Windows. The Linux archives are plain tarballs: one build serves every distribution. There is no .deb or .rpm yet.

| Platform | Architecture | Archive | | --- | --- | --- | | macOS | Apple Silicon | zasper-webapp--darwin-arm64.tar.gz | | macOS | Intel | zasper-webapp--darwin-amd64.tar.gz | | Linux | x86-64 | zasper-webapp--linux-amd64.tar.gz | | Linux | ARM64 | zasper-webapp--linux-arm64.tar.gz | | Linux | i386 | zasper-webapp--linux-386.tar.gz | | Windows | x86-64 | zasper-webapp--windows-amd64.zip | | Windows | ARM64 | zasper-webapp--windows-arm64.zip | | Windows | i386 | zasper-webapp--windows-386.zip |

Each release carries a zasper__checksums.txt. Verify a download with sha256sum -c zasper__checksums.txt --ignore-missing. The same archives are listed on the downloads page.

Docker

An image can be built from docker/; see docker/README.md.

Getting started

Run zasper in the directory you want to work in:

$ zasper
==========================================================
     ███████╗ █████╗ ███████╗██████╗ ███████╗██████╗
     ╚══███╔╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔══██╗
       ███╔╝ ███████║███████╗██████╔╝█████╗  ██████╔╝
      ███╔╝  ██╔══██║╚════██║██╔═══╝ ██╔══╝  ██╔══██╗
     ███████╗██║  ██║███████║██║     ███████╗██║  ██║
     ╚══════╝╚═╝  ╚═╝╚══════╝╚═╝     ╚══════╝╚═╝  ╚═╝

Zasper Server Version: 1.0.0 ---------------------------------------------------------- ✅ Server started successfully! 📡 Bound to: 127.0.0.1:8048 🖥️ Webapp available at: http://127.0.0.1:8048 🔐 Server Access Token: 14be1b674a3b9196a82c01129028d0dd 🔗 Sign in with: http://127.0.0.1:8048/?token=14be1b674a3b9196a82c01129028d0dd ==========================================================

Zasper opens the Sign in with link in your default browser, so you arrive already signed in. It does this on macOS and Windows, and on Linux when there is a display. Pass --no-browser to leave the browser closed, for example on a machine you reach over SSH, and open the link yourself.

Self-hosting

Zasper always runs in protected mode: every route except the health check needs a session, and a session comes from the access token printed at startup.

1. Start the server

zasper --host=0.0.0.0 --no-browser

--host makes the server reachable from other machines. Zasper binds 127.0.0.1 by default; widen it only when you mean to. Bound to 127.0.0.1, it answers only to localhost, 127.0.0.1 and [::1], which stops a web page that points its own domain at your machine from reaching it. Behind a reverse proxy on the same machine, keep the default and name the proxy's host instead: --allow-host=zasper.example.com. With --host=0.0.0.0 the banner reports the wider binding:

📡 Bound to:            0.0.0.0:8048
 🖥️ Webapp available at: http://localhost:8048
 🔐 Server Access Token: 14be1b674a3b9196a82c01129028d0dd
 🔗 Sign in with:        http://localhost:8048/?token=14be1b674a3b9196a82c01129028d0dd

2. Sign in

On your own machine there is nothing to do: Zasper opens your browser already signed in. Otherwise, sign in either way:

  • With the link. Open the Sign in with link from the banner. It signs you in straight
away, and the page removes the token from the address bar as soon as it has used it. Treat the link like a password.
  • With the token. Open Zasper in a browser, which takes you to the sign-in page, and paste the
Server Access Token there.

The banner's links say localhost, because they are written for a browser on the server itself. From another machine, replace localhost with the server's hostname or IP address, for example http://my-server:8048/?token=….

When Zasper's output is not a terminal (under Docker, systemd, or piped to a file), it logs one JSON line instead of the banner; the token is that line's access_token field. ZASPER_LOG_FORMAT=console brings the banner back.

A session lasts 24 hours, after which you sign in again. It is an HttpOnly cookie, so no script running in the page can read it, and Sign out ends it on the server, so a copy of it stops working too. A client that is not a browser can send the token from /auth/login's answer as Authorization: Bearer … instead. After ten wrong tokens in a minute, sign-in from that address waits out the rest of the minute.

!The Zasper sign-in page

3. Keep the token across restarts (optional)

A new access token is generated every time the server starts, and open sessions are signed with a key derived from it — so a restart both changes what you type and signs everyone out. Set the token yourself to keep a link you have handed out working, and to leave signed-in browsers signed in:

export ZASPER_ACCESS_TOKEN=your-access-token

There is nothing else to configure: sessions follow the access token, and changing it signs everyone out on purpose. Sign-outs are remembered only while the server runs, so with the token pinned, a session signed out before a restart works again after it until its 24 hours are up.

Symbolic links

The file browser and the editor follow symbolic links, including ones that lead out of the project, so a data folder linked to a large mount opens like any other. It also means a project containing a link to ~/.ssh shows those files, as a terminal in the project would. Saving over a file that is itself a link out of the project is refused.

Jupyter kernels

Zasper works with any kernel that implements the Jupyter protocol, including:

  • Python, in any virtual environment, conda environment or uv project
  • R (IRkernel)
  • Julia (IJulia)
  • Ruby (IRuby)
  • JavaScript and TypeScript (Deno)
  • Go (GoNB)
To see the kernels installed on your machine, run jupyter kernelspec list. The quickest way to get a Python kernel is:
pip install ipykernel

conda environments

conda create --name my-env -c conda-forge ipykernel
conda activate my-env
python -m ipykernel install --user --name=my-env

uv projects

uv init my-project && cd my-project
uv add ipykernel
uv run python -m ipykernel install --user --name=my-project

Once registered, the environment appears in the Launcher and in each notebook's kernel picker.

Language servers

Errors as you type, completion, hover, go to definition and formatting come from a language server you already have installed. For Python that is basedpyright, pyright or pylsp — whichever is found first, in that order — and for Go gopls, with seven languages served in all. Zasper ships none of them and starts the one it finds, in PATH and in the usual per-project places. The status bar item at the right names the server, its state and, for Python, the interpreter it reads imports with; its menu restarts it, shows its log, or names the command to install one.

Notebook cells are served too: the cells are given to the server as one document, IPython's magics and ! escapes are hidden from it, and the interpreter it reads imports with is the one the notebook's kernel runs.

Python type checking is off by default, as it is in VS Code: undefined names and imports that cannot be found are still reported, and nothing else is. Raise it under Settings → Language servers when you want a type checker's opinion on code you maintain.

See docs/LANGUAGE-SERVERS.md for those modes, what each server is told, and what to do when a correct line is squiggled.

Notebook compatibility

Zasper reads and writes the .ipynb format directly. Two guarantees are worth stating outright:

  • A save produces no spurious diff. A notebook Zasper opens and saves comes out byte-for-byte
the file Jupyter would have written, so saving does not manufacture merge conflicts.
  • A file keeps its own nbformat minor version. A 4.2 notebook is written back as 4.2 rather
than silently upgraded to the newest revision, which is what JupyterLab does. Notebooks in formats 2 and 3 are converted to 4.5 on read.

What renders

| Output | Status | | --- | :---: | | text/plain, stdout/stderr, tracebacks | ✅ | | text/html | ✅ | | image/png | ✅ | | Plotly figures (application/vnd.plotly.v1+json) | ✅ | | ipywidgets (application/vnd.jupyter.widget-view+json) | ✅ | | application/json | ✅ | | Markdown cells: GFM tables, task lists, raw HTML, LaTeX via KaTeX | ✅ | | image/svg+xml | ✅ | | text/latex | ✅ | | image/jpeg | ✅ |

text/latex — what SymPy emits under init_printing() — is typeset with KaTeX, the same renderer markdown cells use. image/svg+xml, which graphviz and networkx produce and which matplotlib produces under %config InlineBackend.figure_format = 'svg', is inlined rather than wrapped in an , so a figure that sizes itself to the cell still can.

Known limitations

  • Widget state is not written into the notebook. Closing and reopening a tab is fine: the kernel
keeps running, and the widgets redraw from it. But the state lives only in the kernel, so once that is gone — after the server restarts, or when the file is opened anywhere else, such as on GitHub — the widget shows a placeholder until the cell is run again. JupyterLab can save widget state into the file so it renders without a kernel; Zasper does not write it yet.
  • A saved output does not run its scripts. HTML and SVG read from a notebook are sanitised; only
output a running kernel sends runs its scripts, which is how Bokeh and HTML Plotly outputs draw themselves, so after a notebook is reopened those cells have to be run again. Zasper does not implement Jupyter's notebook signatures.

Keyboard shortcuts

Every action listed here is also in the command palette (⇧⌘P / Ctrl+Shift+P), which shows each command's chord beside it, so the palette rather than this table is the thing to reach for when you have forgotten one.

Where a row gives two chords for macOS, both work: ⌘ is the usual editor convention, and ⌃ is what Zasper was bound to first.

Global

| Action | macOS | Windows / Linux | | --- | --- | --- | | Show All Commands | ⇧⌘P or ⌃⇧P | Ctrl+Shift+P | | Go to File | ⇧⌘O or ⌃⇧O | Ctrl+Shift+O | | Zoom In | ⌘= or ⌘+ | Ctrl+= or Ctrl++ | | Zoom Out | ⌘- | Ctrl+- | | Reset Zoom | ⌘0 | Ctrl+0 |

Zoom scales the whole window, chrome included. To change only the size of code, terminal text and cell output, use Increase / Decrease Font Size from the palette; those have no chord.

Notebook

| Action | macOS | Windows / Linux | | --- | --- | --- | | Save Notebook | ⌘S or ⌃S | Ctrl+S | | Run Cell | ⌃⏎ | Ctrl+Enter | | Run Cell and Select Next | ⇧⏎ | Shift+Enter | | Insert Cell Above | ⌃⇧A | Ctrl+Shift+A | | Insert Cell Below | ⌃⇧B | Ctrl+Shift+B | | Move Cell Up | ⌃⇧↑ | Ctrl+Shift+Up | | Move Cell Down | ⌃⇧↓ | Ctrl+Shift+Down | | Delete Cell | ⌃⇧D | Ctrl+Shift+D | | Undo Cell Operation | ⇧⌘Z | Ctrl+Shift+Z | | Change Cell to Code | ⌃⇧Y | Ctrl+Shift+Y | | Change Cell to Markdown | ⌃⇧M | Ctrl+Shift+M |

Cell operations are on ⌃⇧ rather than a bare ⌃ on purpose: ⌃A, ⌃B, ⌃E and ⌃K are the system text-editing bindings on macOS, and Ctrl+A is select-all everywhere else, so a bare chord would be swallowed before the cell's editor saw it.

Undo Cell Operation is the notebook's own history: it takes back an inserted, deleted, cut, pasted or retyped cell, and a cleared output. ⌘Z inside a cell is the editor's, and still undoes the text you typed there.

Move Cell Up / Down reorders the notebook and is not the same as **Select Next / Previous Cell**, which only moves the selection. The cell's hover toolbar has both pairs: chevrons move the selection, arrows move the cell. The focus travels with the cell, so the chord can be held to carry one cell several places.

These have no chord and live in the palette: Run All Cells, Cut / Copy / Paste Cell, Select Next / Previous Cell, Change Cell to Raw, Expand or Collapse Output, Clear Cell Output, Clear All Outputs, Interrupt Kernel, Restart Kernel, Restart Kernel and Run All Cells, Reconnect to Kernel and Change Kernel. The notebook toolbar and a cell's hover toolbar reach most of them in one click.

Inside a cell

These belong to the editor rather than to a command, because what they do depends on where the cursor is.

| Key | What it does | | --- | --- | | Tab | Accepts the highlighted completion; with a word to the left of the cursor, asks the kernel for completions; otherwise indents | | ↑ on the first line, ↓ on the last | Moves to the cell above or below | | Double-click, or Enter on a selected markdown cell | Opens its source for editing | | Escape, or running the cell, in a markdown cell | Renders it again |

A single click on a rendered markdown cell only selects it; it stays rendered.

Configuration

Command-line flags

| Flag | Default | Description | | --- | --- | --- | | --cwd | . | Base directory of the project | | --host | 127.0.0.1 | Interface to bind; 0.0.0.0 puts the server on the network | | --port | :8048 | Port to start the server on | | --allow-host | | Other host names a server on loopback answers to, comma-separated, such as a reverse proxy's | | --no-browser | off | Do not open the app in a browser on startup | | --trust | off | Trust the project folder for this run: its kernels, .venv and language servers start without asking. See docs/TRUST.md | | --tracking | true | Send anonymous usage data; see Logging and privacy | | --debug | off | Set the log level to debug | | --version | | Print the version and exit | | --check-update | | Ask zasper.io for the newest version and exit: 0 when up to date, 1 when there is a newer one, 2 when the check failed |

--protected is still accepted but ignored: Zasper always runs in protected mode.

Updates

Once a day the server fetches https://zasper.io/latest.json, which names the newest release. When it is newer than the running version, the startup banner and the status bar say so, and Settings shows what the last check found. A check that fails is silent. The snap does not check, because snapd keeps it up to date. The first launch after an upgrade opens the new version's release notes once.

Environment variables

| Variable | Description | | --- | --- | | ZASPER_ACCESS_TOKEN | A fixed access token, instead of a new one on every start; open sessions survive a restart when it is set | | ZASPER_ALLOWED_HOSTS | Host names added to --allow-host's, comma-separated | | ZASPER_TELEMETRY | 0 or 1 to turn anonymous usage data off or on for this run | | ZASPER_LOG_FORMAT | json or console; by default, console on a terminal and JSON otherwise | | ZASPER_ACCESS_LOG | 1 to log every request, not only the ones that failed | | ZASPER_TRUST_ALL | 1 to trust every folder, as the Docker image does; see docs/TRUST.md |

Logging and privacy

The server writes logs to standard output. Run it with --debug to raise the log level.

Zasper sends a small amount of anonymous usage data: counts of things like notebooks opened, cells run and terminals started. It never sends file names, paths, code, project names, your username or your IP address, and there is no session recording or autocapture. PRIVACY.md lists every event and every property, and explains how the allowlist that enforces it works. To turn tracking off:

zasper --tracking=false     # this run
ZASPER_TELEMETRY=0 zasper   # this run, from the environment

Or clear Settings → Privacy → Send anonymous usage data, which is remembered. With tracking off nothing is collected and no request is made.

Architecture

!Zasper architecture

Building from source

Requires Go 1.26+ and Node.js 22.12+ (.nvmrc pins the Node version, so nvm use picks it up).

git clone https://github.com/zasper-io/zasper
cd zasper
make install   # install the frontend's dependencies, build, and install the zasper binary

make install puts zasper in your Go binary directory, so make sure that is on your PATH, then check the install with zasper --version. make build builds the binary in the repository instead, make dev runs the frontend and backend in development mode, and make test runs both test suites. make on its own lists the targets. CONTRIBUTING.md covers the development workflow.

Roadmap

  • Rendering for the outputs listed as not yet under What renders, first after
1.0.
  • Support for data apps, beyond Jupyter notebooks.
  • Easier integration with existing tools.
  • Zasper Hub, for self-hosted deployment in the cloud.

Documentation

1.0.0 onwards. they are told, and what they give a notebook that a file does not get.
  • docs/RUNS.md: how a cell's output survives the browser going away, and reaches
the file when nobody was watching. and in the Variables panel — read from the kernel, including how polars frames are handled. panel, cell by cell, and how its cells are matched.
  • docs/TRUST.md: trusted folders and restricted mode — what runs when a folder opens,
how Zasper asks first, and what the server refuses until it is trusted. in the status bar and the Jupyter info panel, and how it is measured on each platform.
  • docs/SQL.md: SQL cells and data connections — the magic a SQL cell is saved as,
where connections and their passwords are kept, and the Data panel.

Community and support

  • Questions and discussion: join the Zasper community on
Slack.
  • Bugs and feature requests: open an issue.
  • Sponsorship: support Zasper's development through
GitHub Sponsors.

Acknowledgements

Zasper's development has been supported by a grant from FOSS United and Zerodha.

FOSS United      Zerodha

Zasper would not exist without the Jupyter community. It uses the Jupyter wire protocol and draws on Jupyter's architecture, and our thanks go to everyone who has built that foundation.

Contributing

Contributions of every kind are welcome: bug reports, documentation, and pull requests or reviews of them. CONTRIBUTING.md explains how to build Zasper and submit changes. Before your first pull request is merged, you will be asked to sign the Contributor License Agreement. Everyone taking part is expected to follow the Code of Conduct.

Contributors

License

Copyright © 2024–2026 Prasun Anand.

Zasper is dual-licensed. You can use it under either of these:

  • Open source: the GNU Affero General Public License, version 3 only (AGPL-3.0-only).
See LICENSE. Running Zasper, on your own machine or on a server, needs nothing more. If you distribute Zasper, or let people use a modified version over a network, the AGPL requires you to make the corresponding source, including your changes, available under the same license.
  • Commercial: for organizations that want to build Zasper into a proprietary product,
keep their modifications private, or need terms the AGPL does not offer. Write to [email protected].

Contributions are accepted under the Contributor License Agreement, which is what lets Zasper be offered under both licenses.

Chat with me