Thanks for contributing to JupyterLite!
We follow Project Jupyter's Code of Conduct for a friendly and welcoming collaborative environment.
You'll need:
gitnodejs >=24,<25python >=3.10
Tip: You can use any Python package manager you prefer (pip, conda, etc.) for
installing Python dependencies.
Install all dependencies and set up the dev environment:
# 1. Install build dependencies (includes JupyterLab which provides `jlpm`)
pip install --group build
# 2. Install Node.js dependencies and Python packages
jlpm install
jlpm install:pyThe jlpm install:py command installs remaining Python dependencies and packages in
editable mode.
You can also install dependencies manually:
pip install --group dev # Core dev tools
pip install --group all-dev # All dev dependencies (dev + docs + lint + test)
# Python package in editable mode
pip install -e './py/jupyterlite-core[all,test]'Available dependency groups:
| Group | Description |
|---|---|
build |
Minimal build dependencies (hatch, jupyterlab) |
dev |
Core development (includes build) |
docs |
Documentation building (Sphinx, themes) |
lint |
Linting tools (ruff, pre-commit) |
test |
Testing (pytest and plugins) |
demo |
Demo site extensions (widgets, libraries) |
ui-tests |
UI/Playwright tests (includes build) |
release |
Release process (includes build + lint) |
all-dev |
All dev dependencies (dev + docs + lint + test) |
Note: Dependency groups (PEP 735) require pip 24.1+ or uv.
Then run the build command:
jlpm build| Command | Description |
|---|---|
jlpm dev:watch |
Build and watch for changes (creates _site/ directory) |
jlpm dev:serve |
Serve _site/ on http://localhost:8000 |
jlpm dev:build |
One-off build (no watching) |
The recommended workflow uses two terminal windows:
# Terminal 1: Watch and rebuild on changes
jlpm dev:watch
# Terminal 2: Serve the site (run after Terminal 1 creates _site/)
jlpm dev:serveRefresh your browser after changes. For Python package changes or new extensions,
restart dev:watch.
| Directory | Purpose |
|---|---|
app/ |
The JupyterLite application shell (built from packages/) |
packages/ |
TypeScript source packages (core libraries, plugins, extensions) |
py/ |
Python packages (CLI) (jupyterlite-core, jupyterlite) |
examples/ |
Demo content: notebooks, jupyter_lite_config.json, and requirements |
_site/ |
Build output directory (contains build/ symlink to app/build/) |
ui-tests/ |
Playwright end-to-end and visual regression tests |
docs/ |
Sphinx documentation source |
Install kernels and extensions with pip:
pip install jupyterlite-pyodide-kernel
jlpm dev:build # Rebuilds site with the new kernelThe repository has integrity checks to ensure consistency across package files.
Each app (app/lab, app/notebooks, etc.) has a resolutions field in its
package.json that pins dependency versions. Resolutions must stay in sync with
dependencies - if you update a dependency version, the corresponding resolution must
also be updated.
jlpm integrityRun jlpm integrity after updating dependencies (e.g., bumping JupyterLab versions) to
sync the resolution entries.
The yarn.lock file should be deduplicated to minimize dependency duplication:
jlpm deduplicateTo update a dependency across the monorepo:
jlpm up "<package-pattern>"For example, to update all @jupyterlab/* packages to the latest version:
jlpm up "@jupyterlab/*"After updating dependencies, run jlpm integrity to sync app resolutions.
When a new JupyterLab or Notebook release is available, use the upgrade script to update
dependencies across the repository. At least one of --jupyterlab-version or
--notebook-version must be specified:
# Update JupyterLab to latest stable
python scripts/upgrade-dependencies.py --jupyterlab-version latest
# Update Notebook to latest stable
python scripts/upgrade-dependencies.py --notebook-version latest
# Update both to latest stable
python scripts/upgrade-dependencies.py --jupyterlab-version latest --notebook-version latestThis script:
- Fetches the specified releases from GitHub
- Updates version constraints in
pyproject.toml - Updates all
@jupyterlab/*,@lumino/*,@jupyter/*, and@jupyter-notebook/*dependencies inpackage.jsonfiles to match upstream versions - Only updates packages for which a version argument is provided
# Preview changes without modifying files
python scripts/upgrade-dependencies.py --jupyterlab-version latest --dry-run
# Update to the latest pre-release versions
python scripts/upgrade-dependencies.py --jupyterlab-version next --notebook-version next
# Update to specific versions
python scripts/upgrade-dependencies.py --jupyterlab-version 4.4.0 --notebook-version 7.4.0jlpm install # Update yarn.lock
jlpm deduplicate # Clean up duplicate dependencies
jlpm integrity # Sync app resolutions
jlpm build # Verify the build succeedsSet the GITHUB_TOKEN environment variable to avoid GitHub API rate limiting
(unauthenticated requests are limited to 60/hour, authenticated to 5,000/hour). For
local development, create a Personal Access Token:
- Classic PAT: No scopes required (public repo read access is implicit)
- Fine-grained PAT: Select "Public Repositories (read-only)"
In GitHub Actions, the CI workflow uses PERSONAL_GITHUB_TOKEN (a PAT stored as a
repository secret) to create PRs. This is required because PRs created with the
automatic GITHUB_TOKEN won't trigger CI checks on the PR itself.
You can trigger the upgrade workflow directly from GitHub Actions from a fork, which automates the entire process (running the script, updating lock files, and creating a PR).
Before running the workflow from a fork, you need to create a PERSONAL_GITHUB_TOKEN
repository secret:
- Create a Personal Access Token with these
permissions:
- Classic PAT:
reposcope (to push branches and create PRs) - Fine-grained PAT: Select the target repository with "Contents" (read/write) and "Pull requests" (read/write) permissions
- Classic PAT:
- Go to your fork's Settings → Secrets and variables → Actions
- Click New repository secret
- Name it
PERSONAL_GITHUB_TOKENand paste your token
- Go to Actions → Upgrade JupyterLab/Notebook dependencies in the GitHub repository
- Click "Run workflow"
- Fill in the parameters:
- JupyterLab version: Version number or
latest(default:latest) - Notebook version: Version number or
latest(default:latest) - Branch: Target branch for the PR (default:
main) - Target repository: Where to create the PR (default:
jupyterlite/jupyterlite)
- JupyterLab version: Version number or
- Click "Run workflow"
The workflow will create a PR with all necessary changes if updates are available.
The demo site extensions are defined in the demo dependency group in pyproject.toml.
| File | Purpose |
|---|---|
pyproject.toml (demo group) |
Source of truth for demo extensions |
examples/requirements-demo.txt |
Lock file with pinned versions (generated) |
examples/requirements-piplite.txt |
Additional packages for piplite bundling |
examples/jupyter_lite_config.json |
Contains generated piplite_urls |
When you change the demo dependency group in pyproject.toml, regenerate the lock
file and piplite URLs:
python scripts/compile-lock-files.pyThis will update both examples/requirements-demo.txt and the piplite_urls in
examples/jupyter_lite_config.json. Commit these generated files.
Note: This script uses uv if available, otherwise falls back to pip-compile
(from pip-tools).
Install the docs dependency group first:
pip install --group docs| Command | Description |
|---|---|
jlpm docs:build |
Build Sphinx documentation |
jlpm docs:serve |
Serve docs on http://localhost:8000 |
jlpm docs:watch |
Watch mode with auto-rebuild |
jlpm test:pyJupyterLite uses Galata for end-to-end and visual regression testing.
Note: Complete the Quick Start setup first - UI tests require
jupyterlite-coreto be installed.
# Install the ui-tests dependencies
pip install --group ui-tests
cd ui-tests
# Install dependencies
jlpm
# Install Playwright browsers
jlpm playwright install
# Build the JupyterLite app used in the tests
jlpm build
# Run the tests
jlpm testTo run in headed mode:
jlpm test --headedTo update snapshots:
jlpm test:updateSee Playwright Command Line Reference for more options.