Thanks for your interest in contributing. This document covers building, running, testing and packaging the application. Elsewhere:
- Cutting a release, and updating the bundled JupyterLab: Release.md
- Reporting a bug: open an issue with the bug report template, after checking the troubleshooting guide
- Using the app: user guide, CLI documentation, Python environment management
JupyterLab Desktop packages JupyterLab as an Electron application. A change to the notebook interface itself belongs in that repository; this one covers the desktop shell, the bundled Python environment and the installers.
Pull requests follow the template, which includes a section on AI usage. Answer both of its questions honestly, and keep the pull request in draft until you have run the code yourself.
This project follows the Jupyter Code of Conduct. Security vulnerabilities never go to a public issue: the Jupyter security policy asks for a GitHub Security Advisory on this repository, and security@jupyter.org only when that is not possible.
-
You can install
condaas part of a Miniforge installer. -
conda pack and conda lock to bundle JupyterLab Desktop Server into the standalone application and to create lock files. You can install them using:
conda install -c conda-forge conda-pack conda-lock
-
nodejs
You can install from https://nodejs.org/en/download/ or run:
conda install -c conda-forge nodejs
-
yarn
Install using
npm install --global yarn
JupyterLab Desktop bundles JupyterLab front-end and a conda environment as JupyterLab Desktop Server as its backend into an Electron application.
<platform>: osx-64, osx-arm64, linux-64, linux-aarch64 or win-64. The dist scripts also take osx, for both macOS architectures at once. package.json is the list. dist:win-arm64 exists but passes no --win, unlike every sibling, so on a non-Windows host it builds for the host platform instead and says nothing.
-
Get the project source code
git clone https://github.com/jupyterlab/jupyterlab-desktop.git
-
Install dependencies and build JupyterLab Desktop
yarn yarn build
-
Create the JupyterLab Desktop Server installer using
yarn create_env_installer:<platform>
Installer will be created in
env_installer/jlab_server.tar.gzand will be available for use inenv_installer/jlab_server. -
Now you can launch the JupyterLab Desktop locally using:
yarn start
If JupyterLab Desktop does not find a compatible Python environment configured, it will prompt for installation using JupyterLab Desktop Server installer or let you choose a custom environment on your computer at first launch.
Unit tests are Vitest and live in test/unit. End-to-end tests are Playwright driving the real Electron app and live in test/e2e.
yarn test:unit # vitest run
yarn test:unit:watch # vitest, re-runs on change
yarn test:coverage # vitest run --coverage, enforces the thresholds below
yarn test:e2e # playwright test, requires yarn build firstyarn test:e2e launches the built entry point rather than the sources: package.json points main at ./build/out/main/main.js, and the tests call electron.launch against the project directory. On a clean checkout the run fails on a missing bundle rather than on a real defect, so run yarn build before it.
It also needs a Python with JupyterLab, pointed at by JLAB_TEST_PYTHON_PATH. Without it the env-backed specs skip rather than fail, so the suite reports green while the tests that exercise environments never ran. .github/workflows/e2e.yml builds a venv for this, and the same thing locally is:
python3 -m venv /tmp/jlab-venv
# the same pin the app bundles, which is what CI installs
ver=$(awk '$1=="-" && $2=="jupyterlab" {print $3}' env_installer/jlab_server.yaml)
/tmp/jlab-venv/bin/pip install "jupyterlab==$ver" ipywidgets
export JLAB_TEST_PYTHON_PATH=/tmp/jlab-venv/bin/pythonInstalling an unpinned JupyterLab works until it drifts from the bundled one, at which point the failure is version skew that CI cannot reproduce.
On Linux, Playwright needs its system libraries, and the app needs a display: npx playwright install-deps, then run the suite under xvfb-run -a. The app opens several windows, so a real display beats headless. macOS runners have one already.
Coverage is configured in vitest.config.ts with an explicit include list, which is what makes untested branches in those files count against the thresholds even when no test imports them: Vitest instruments every file the list matches, not only the ones a test reached. The list is scoped to the main-process logic modules, and leaves out the window, view, dialog and preload surfaces so that code a unit test cannot reach without a running Electron process does not dilute the denominator. Being outside the list is not the same as being untested: test/unit/preload alone holds twelve specs, and several of those surfaces have unit tests of their own. It only means no coverage floor is enforced on them. Besides the aggregate floor, several well-covered modules are locked at their current level so a later change cannot silently regress them.
The unit suite runs in CI as well: publish.yml runs yarn test:coverage on Linux, where the thresholds are enforced, and yarn test:unit on macOS and Windows, and its publish job is needs: test. A regressed threshold turns up there rather than here if you skip it.
Three more checks run in CI and are worth running before pushing:
yarn type-check # tsc --noEmit, run by typecheck.yml
yarn lint:check # prettier --check and eslint, no writes, run by publish.yml
yarn check_version_match # desktop against bundled JupyterLab version, run by publish.ymlyarn lint is the same two with fixes applied, and CI never runs it, since a job that rewrites the tree would have nowhere to put the result. It also starts with a bare yarn, so it reinstalls dependencies and can rewrite yarn.lock; lint:check does not.
Prettier is pinned in devDependencies and its config is .prettierrc. The trap is the config rather than the binary: run against a file outside the project tree, prettier finds no .prettierrc to inherit, falls back to its defaults and reports wrapping differences that do not exist. Pass the config explicitly when checking anything that is not in place: ./node_modules/.bin/prettier --config ./.prettierrc --check <file>.
-
Build the application
yarn run clean && yarn build -
Create JupyterLab Desktop Server installer
yarn create_env_installer:<platform>
-
Create JupyterLab Desktop installer which will also bundle JupyterLab Desktop Server installer.
yarn dist:<platform>
Application Installer will be created in
dist/JupyterLab-arm64.dmgordist/JupyterLab-x64.dmg(macOS, which names the architecture),dist/JupyterLab.deb(Debian, Ubuntu),dist/JupyterLab.rpm(Red Hat, Fedora) anddist/JupyterLab-Setup.exe(Windows) based on the platform
These questions already have one answer, so please do not write a second one:
| Question | Where it is answered |
|---|---|
| What origin is this URL? | originOf, isSameServerOrigin in src/main/utils.ts |
| Is this a scheme I accept? | matchesScheme in src/main/utils.ts |
| May this surface navigate there? | guardNavigation in src/main/navigationguard.ts, navigationpolicy.ts |
| Should this link leave the app? | openUrlInSystemBrowser in src/main/navigationguard.ts |
A webContents nobody claims cannot navigate at all, so a new view is safe until markGuarded opts it into a policy of its own.
Expected manual testing coverage depends on the PR, when pulling:
- patch releases of JupyterLab or Electron: Testing on a single OS is sufficient.
- minor or major JupyterLab releases and minor Electron releases: Test on multiple OSes.
- major Electron releases: Test on all OSes.
A release PR must be approved by at least two people.
Depending on the PR, different part of the application may require testing. Use the guide below, but exercise your own judgment to skip or add more checks depending on circumstances.
For patch dependency update PRs:
- Notebooks UI launches (smoke test, no extensive testing required)
For minor and major JupyterLab update PRs:
- JupyterLab Desktop theme switching works
- UI Mode switching works
For conda update PRs:
- Creating new environments from "Manage Python Environments" dialog works
- The environment picker popover shows up with a list of environments
- Switching environments works
For minor and major Electron updates PR:
- All checks listed above
- No new errors in log files (e.g.
~/Library/Logs/jupyterlab-desktop/main.log) and when launching from terminal withjlab - The welcome screen opens, displays the news feed, recent sessions, and allows to create new sessions
- The settings window opens
Before JupyterLab Desktop release:
- All checks listed above
For instructions on updating bundled JupyterLab packages and cutting a new release, please follow Release.md document.