<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jupyter Blog - extensions</title><link href="https://jasongrout.github.io/medium-archive/pelican/" rel="alternate"/><link href="https://jasongrout.github.io/medium-archive/pelican/feeds/tag-extensions.atom.xml" rel="self"/><id>https://jasongrout.github.io/medium-archive/pelican/</id><updated>2026-06-19T13:11:00+00:00</updated><subtitle>The Project Jupyter blog: news, releases, and community stories, archived from blog.jupyter.org.</subtitle><entry><title>Announcing jupyter-builder: A Standalone Build System for JupyterLab Extensions</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/announcing-jupyter-builder-a-standalone-build-system/" rel="alternate"/><published>2026-06-19T13:11:00+00:00</published><updated>2026-06-19T13:11:00+00:00</updated><author><name>Darshan Kr. Paudyal</name></author><id>tag:jasongrout.github.io,2026-06-19:/medium-archive/pelican/posts/2026/announcing-jupyter-builder-a-standalone-build-system/</id><summary type="html">&lt;p&gt;We’re excited to announce the first stable release of jupyter-builder on PyPI and @jupyter/builder on npm. A standalone, configurable build…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/announcing-jupyter-builder-a-standalone-build-system/images/001-1_3UBtK-sh01N6OXIbGFNrpQ.webp" alt="Jupyter Builder logo" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;We’re excited to announce the first stable release of &lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/a&gt; on PyPI and &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt; on npm. A standalone, configurable build system for JupyterLab and its extensions.&lt;/p&gt;
&lt;p&gt;If you build or maintain a JupyterLab extension, this release is for you.&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;For a long time, building a JupyterLab extension has meant installing all of JupyterLab. Take a look at any extension’s build dependencies and you’ll see &lt;code&gt;jupyterlab&lt;/code&gt; listed there, not because the extension needs JupyterLab at runtime to be built, but because the build tooling lives &lt;em&gt;inside&lt;/em&gt; the JupyterLab repository. The &lt;code&gt;builder/&lt;/code&gt; folder, the &lt;code&gt;jlpm&lt;/code&gt; command, the helper scripts, they’re all coupled to the JupyterLab core.&lt;/p&gt;
&lt;p&gt;This setup has caused real friction over the years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linux packagers&lt;/strong&gt; have repeatedly raised concerns about the circular dependency where extensions like &lt;code&gt;jupyterlab_pygments&lt;/code&gt; need &lt;code&gt;jupyterlab&lt;/code&gt; to build, but &lt;code&gt;jupyterlab&lt;/code&gt; ships with those extensions. It’s a chicken-and-egg problem that makes packaging painful.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extension authors&lt;/strong&gt; have to update their build system every time they update to a new JupyterLab version, which sometimes introduces breakages that have nothing to do with their extension’s actual code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Users&lt;/strong&gt; still occasionally run into installation issues that trace back to the incomplete migration from “source” extensions to prebuilt ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The need to separate the build system was identified back in &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/13456"&gt;jupyterlab#13456&lt;/a&gt;, and initial work began during GSoC 2024. With the support of the Jupyter Foundation’s first community-funded proposal, we’ve now brought that work to a stable release.&lt;/p&gt;
&lt;h2 id="what-jupyter-builder-does"&gt;What &lt;code&gt;jupyter-builder&lt;/code&gt; does&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; extracts all the Node.js-based build tooling out of JupyterLab into a dedicated package. You install it on its own, and it builds your extension, no full JupyterLab installation required.&lt;/p&gt;
&lt;p&gt;It ships in two parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;strong&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; (PyPI) — the Python package that provides the &lt;code&gt;jupyter-builder&lt;/code&gt; CLI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;strong&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; (npm) — the underlying JavaScript package containing the build scripts. The Python CLI invokes &lt;code&gt;build-labextension.js&lt;/code&gt; from this package, which then runs the &lt;a href="https://rspack.dev/"&gt;rspack&lt;/a&gt; compiler with all the configuration needed to produce a JupyterLab extension.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of &lt;code&gt;jupyter labextension build .&lt;/code&gt;, you now run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupyter-builder&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="build-compatibility-without-jupyterlab-installed"&gt;Build compatibility without JupyterLab installed&lt;/h2&gt;
&lt;p&gt;One interesting problem we had to solve: if JupyterLab isn’t installed, how does the builder verify that an extension is compatible with a given JupyterLab version?&lt;/p&gt;
&lt;p&gt;The answer is &lt;a href="https://www.npmjs.com/package/@jupyterlab/core-meta"&gt;&lt;code&gt;@jupyterlab/core-meta&lt;/code&gt;&lt;/a&gt;, a small npm package that publishes JupyterLab’s core metadata on its own. &lt;code&gt;@jupyter/builder&lt;/code&gt; declares a dependency on a specific &lt;code&gt;@jupyterlab/core-meta&lt;/code&gt; version, so by default your extension is checked for compatibility against whatever JupyterLab version that pin corresponds to. In general, the latest &lt;code&gt;@jupyter/builder&lt;/code&gt; will track the latest JupyterLab.&lt;/p&gt;
&lt;p&gt;If you want to build against a different JupyterLab version, pass &lt;code&gt;--core-version&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupyter-builder&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--core-version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.4.x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’ll download that version’s metadata from &lt;code&gt;@jupyterlab/core-meta&lt;/code&gt; on npm and check compatibility against it.&lt;/p&gt;
&lt;h2 id="migrating-an-existing-extension"&gt;Migrating an existing extension&lt;/h2&gt;
&lt;p&gt;If you’re starting a new extension with the &lt;a href="https://github.com/jupyterlab/extension-template"&gt;extension template&lt;/a&gt;, you’ll get all of this set up automatically. For existing extensions, there are two changes to make.&lt;/p&gt;
&lt;h2 id="1-update-the-python-build-dependencies"&gt;1. Update the Python build dependencies&lt;/h2&gt;
&lt;p&gt;In your &lt;code&gt;pyproject.toml&lt;/code&gt;, swap &lt;code&gt;jupyterlab&lt;/code&gt; for &lt;code&gt;jupyter-builder&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;hatchling&amp;gt;=1.5.0&amp;quot;, &amp;quot;jupyterlab&amp;gt;=4.0.0,&amp;lt;5&amp;quot;, &amp;quot;hatch-nodejs-version&amp;gt;=0.3.2&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# After&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;hatchling&amp;gt;=1.5.0&amp;quot;, &amp;quot;jupyter-builder&amp;gt;=1.0.0&amp;quot;, &amp;quot;hatch-nodejs-version&amp;gt;=0.3.2&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then update the &lt;code&gt;scripts&lt;/code&gt; section of your &lt;code&gt;package.json&lt;/code&gt; to use the new CLI:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;build:labextension&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder build .&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;build:labextension:dev&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder build --development True .&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;watch:labextension&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder watch .&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Your build no longer pulls in all of JupyterLab.&lt;/p&gt;
&lt;h2 id="2-update-the-npm-builder-dependency"&gt;2. Update the npm builder dependency&lt;/h2&gt;
&lt;p&gt;Replace &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;@jupyter/builder&lt;/code&gt; in your &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;@jupyter/builder&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^1.0.0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; is backwards-compatible with &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt;, so things will keep working if you don’t migrate immediately. That said, we strongly recommend switching: &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt; is no longer being actively developed, and all new work is happening in &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that as part of this transition JupyterLab has also moved from Webpack to &lt;a href="https://rspack.rs/"&gt;Rspack&lt;/a&gt;. Most extensions won’t need any changes, but if yours uses a custom &lt;code&gt;webpackConfig&lt;/code&gt; see the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#building-extensions-with-rspack"&gt;Rspack migration notes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s next&lt;/h2&gt;
&lt;p&gt;This release is the first stable foundation. We’re continuing to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migrate more packages across the &lt;a href="https://github.com/jupyterlab/"&gt;Jupyter organization&lt;/a&gt; to use &lt;code&gt;jupyter-builder&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make the builder more configurable, so it can eventually be used for projects beyond JupyterLab.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you maintain an extension, please try the migration and &lt;a href="https://github.com/jupyterlab/jupyter-builder/issues"&gt;let us know how it goes&lt;/a&gt;. Feedback from real extensions is the best way to make sure the migration path stays smooth.&lt;/p&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;This work was funded under the &lt;a href="/posts/2025/announcing-our-first-jupyter-community-funded-proposals/"&gt;first round of Jupyter Community Funded Proposals&lt;/a&gt;. We want to thank the &lt;a href="https://jupyterfoundation.org/"&gt;Jupyter Foundation&lt;/a&gt; and everyone involved in setting up this funding mechanism.&lt;/p&gt;
&lt;p&gt;A huge thank you to &lt;a href="https://github.com/bollwyvl"&gt;Nicholas Bollweg&lt;/a&gt; for many thoughtful discussions on the design and packaging of &lt;code&gt;jupyter-builder&lt;/code&gt;, and to &lt;a href="https://github.com/jtpio"&gt;Jeremy Tuloup&lt;/a&gt; for helping us resolve issues and for guiding the adoption of &lt;code&gt;jupyter-builder&lt;/code&gt; in Jupyter Notebook.&lt;/p&gt;
&lt;p&gt;This package was initially created during &lt;a href="https://summerofcode.withgoogle.com/archive/2024/projects/YMB3Rc53"&gt;GSoC 2024&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/ronan-coutinho/"&gt;Ronan Coutinho&lt;/a&gt;, mentored by &lt;a href="https://www.linkedin.com/in/fcollonval/"&gt;Frédéric Collonval&lt;/a&gt;. Their work laid the foundation that made this release possible.&lt;/p&gt;
&lt;h2 id="about-the-developers"&gt;About the developers&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/darshan-poudel8/"&gt;&lt;strong&gt;Darshan Paudyal&lt;/strong&gt;&lt;/a&gt; is an intern at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt; and a member of the &lt;a href="https://jupyterlab-team-compass.readthedocs.io/en/latest/team.html"&gt;Jupyter Frontend Council&lt;/a&gt;. Darshan led the work to bring &lt;code&gt;jupyter-builder&lt;/code&gt; to a stable release, completing the separation from JupyterLab core and shepherding the package through to its first published version.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/michal-krassowski/"&gt;&lt;strong&gt;Michał Krassowski&lt;/strong&gt;&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;. Mike provided guidance and technical direction throughout the development of &lt;code&gt;jupyter-builder&lt;/code&gt;, helping shape its architecture and roadmap.&lt;/p&gt;
&lt;h2 id="useful-links"&gt;Useful links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📦 npm: &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💻 Source: &lt;a href="https://github.com/jupyterlab/jupyter-builder"&gt;github.com/jupyterlab/jupyter-builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📖 Migration guide: &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#migrating-to-jupyter-builder"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; migration section&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💬 Issues &amp;amp; feedback: &lt;a href="https://github.com/jupyterlab/jupyter-builder/issues"&gt;GitHub issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>A User’s Journey with Plugin Playground: From First Idea to Installable JupyterLab Extension.</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/" rel="alternate"/><published>2026-05-28T17:45:00+00:00</published><updated>2026-05-28T18:07:00+00:00</updated><author><name>Anuj Singh</name></author><id>tag:jasongrout.github.io,2026-05-28:/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/</id><summary type="html">&lt;p&gt;We are excited to announce the 1.0 release of JupyterLab Plugin Playground, allowing you to seamlessly experiment with the creation of…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We are excited to announce the 1.0 release of JupyterLab Plugin Playground, allowing you to seamlessly experiment with the creation of Jupyter Notebook and JupyterLab plugins to add any functionality you may desire. To install:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install jupyterlab-plugin-playground
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or use it from Binder or JupyterLite without installing anything by clicking one of these links: &lt;a href="https://mybinder.org/v2/gh/jupyterlab/plugin-playground/main?urlpath=lab"&gt;Binder (Lab)&lt;/a&gt;, &lt;a href="https://mybinder.org/v2/gh/jupyterlab/plugin-playground/main?urlpath=tree"&gt;Binder (Notebook v7)&lt;/a&gt;, &lt;a href="https://jupyterlab-plugin-playground.readthedocs.io/en/latest/lite/lab/"&gt;JupyterLite (Lab)&lt;/a&gt;, &lt;a href="https://jupyterlab-plugin-playground.readthedocs.io/en/latest/lite/tree/"&gt;JupyterLite (Notebook v7)&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="motivation"&gt;&lt;strong&gt;Motivation&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Building a JupyterLab extension has traditionally required setting up a complex local environment: installing dependencies from disjoint ecosystems (Node.js and Python), configuring tooling, and hunting down the right documentation. The goal of the Playground is to eliminate that setup overhead and streamline extension development. By bringing the runtime, documentation, and the context required for extension development into a single browser tab, you can go from idea to working extension without any local setup required.&lt;/p&gt;
&lt;h2 id="journey-at-a-glance"&gt;&lt;strong&gt;Journey At a Glance&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start with a tiny extension idea that is easy to verify visually.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose a build path: &lt;strong&gt;Manual&lt;/strong&gt; or &lt;strong&gt;AI-assisted&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Load&lt;/strong&gt; and iterate inside JupyterLab until behavior is stable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build a second extension to prove the workflow is repeatable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Share plugin files/packages for product and engineering review.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export as a &lt;strong&gt;wheel(.whl)&lt;/strong&gt; and validate installation in a clean Binder runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export as a &lt;strong&gt;zip&lt;/strong&gt; and move the scaffold into a normal GitHub repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="10-pick-your-starting-path"&gt;&lt;strong&gt;1.0: Pick Your Starting Path&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;At the beginning, choose the path that matches your confidence level and speed needs. If this is your first time with Plugin Playground, you can optionally run &lt;code&gt;Take the Tour&lt;/code&gt; from the Launcher or Command Palette for quick orientation before starting.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Start from File&lt;/code&gt; If you already know the APIs you need and want direct control.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Build with AI&lt;/code&gt; If you know the intended behavior but want a first draft quickly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both paths converge to the same shipping workflow later.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/001-1_ejDftHO8TiktbPMim-EVqA.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="20-build-extension-1-manually"&gt;&lt;strong&gt;2.0: Build Extension #1 Manually&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;For the first pass, we will create something small and observable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our Goal&lt;/strong&gt;: add a command named &lt;code&gt;Toggle Right Sidebar&lt;/code&gt; to the command palette.&lt;/p&gt;
&lt;h3 id="21-create-the-plugin-file"&gt;&lt;strong&gt;2.1: Create the plugin file&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Launch &lt;strong&gt;Start from File&lt;/strong&gt;. This comes with a dummy template.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/application&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ow"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hello-world:plugin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;autoStart&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;activate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="22-fill-the-missing-pieces-using-the-right-sidebar"&gt;&lt;strong&gt;2.2: Fill the missing pieces using the right sidebar&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now build the plugin in order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;Tokens&lt;/code&gt;section in the right sidebar, search &lt;code&gt;ICommandPalette&lt;/code&gt; and click insert ( + icon ), so Plugin Playground adds token import and dependency wiring.&lt;/li&gt;
&lt;li&gt;Add a simple command skeleton inside the &lt;strong&gt;activate&lt;/strong&gt; function:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-first-playground-plugin:toggle-sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Toggle Right Sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// place cursor here&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="3"&gt;
&lt;li&gt;In &lt;code&gt;Commands&lt;/code&gt;, search &lt;code&gt;application:toggle-right-area&lt;/code&gt;, place your cursor inside &lt;code&gt;execute&lt;/code&gt; of the added command ( see above ) and use &lt;code&gt;Insert in selection&lt;/code&gt; This adds at the cursor position:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application:toggle-right-area&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Register the command in the palette:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AAA&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After those edits, your file should look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ICommandPalette&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/apputils&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/application&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ow"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hello-world:plugin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;autoStart&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;requires&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ICommandPalette&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;activate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ICommandPalette&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-first-playground-plugin:toggle-sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Toggle Right Sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application:toggle-right-area&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AAA&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Uses the typed plugin template (&lt;code&gt;JupyterFrontEndPlugin&amp;lt;void&amp;gt;&lt;/code&gt;) and typed &lt;code&gt;activate&lt;/code&gt; parameters. Imports &lt;code&gt;ICommandPalette&lt;/code&gt; so the plugin can add entries to the Command Palette.&lt;/li&gt;
&lt;li&gt;Registers a new command (&lt;code&gt;my-first-playground-plugin:toggle-sidebar&lt;/code&gt;) and executes &lt;code&gt;application:toggle-right-area&lt;/code&gt; when your command runs.&lt;/li&gt;
&lt;li&gt;Adds your command to the palette under the category &lt;code&gt;AAA&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="23-load-it-and-verify-behavior"&gt;&lt;strong&gt;2.3: Load it and verify behavior&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Click &lt;strong&gt;Load Current File As Extension or click the run button in the toolbar&lt;/strong&gt;, and open Command Palette.&lt;/p&gt;
&lt;p&gt;When you run &lt;strong&gt;Load Current File As Extension&lt;/strong&gt;, Plugin Playground compiles the active file and registers the plugin object into the live JupyterLab session. During iteration, if a plugin with the same &lt;strong&gt;id&lt;/strong&gt; already exists, Plugin Playground attempts to deactivate and replace it so reload loops stay fast.&lt;/p&gt;
&lt;h3 id="expected-result"&gt;&lt;strong&gt;Expected result&lt;/strong&gt;:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Toggle Right Sidebar&lt;/strong&gt; appears in the command palette ( open command palette by going through &lt;strong&gt;view -&amp;gt; Activate command palette&lt;/strong&gt; or Press &lt;strong&gt;Accel&lt;/strong&gt; + &lt;strong&gt;shift&lt;/strong&gt; + &lt;strong&gt;C&lt;/strong&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/002-1_RXMnfrUXeu1q7AkukZEF_A.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Running it hides/shows the JupyterLab right sidebar.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="30-build-with-ai"&gt;&lt;strong&gt;3.0: Build with AI&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If you want a faster draft, use AI assistance for the same extension workflow.&lt;/p&gt;
&lt;p&gt;Goal: add the &lt;strong&gt;Show Active Notebook Cell Count&lt;/strong&gt; command with a friendly no-notebook fallback.&lt;/p&gt;
&lt;h3 id="31-give-ai-a-precise-request"&gt;&lt;strong&gt;3.1: Give AI a precise request&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Use &lt;strong&gt;Build with AI&lt;/strong&gt; and give a prompt:&lt;/p&gt;
&lt;p&gt;for example, something like:-&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Create a JupyterLab plugin named &amp;quot;active-notebook-cell-counter&amp;quot;.
Add a command &amp;quot;Show Active Notebook Cell Count&amp;quot;.
If no notebook is active, show a friendly dialog message.
If a notebook is active, show the current cell count in a dialog.
Register the command in the command palette under category &amp;quot;Playground Demo&amp;quot;.
Export default plugin object.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="what-this-block-does"&gt;What this block does:&lt;/h3&gt;
&lt;p&gt;This gives AI a concrete plugin goal, command name, and fallback behavior requirements.&lt;/p&gt;
&lt;h3 id="32-use-ai-deterministic-inserts-together"&gt;&lt;strong&gt;3.2: Use AI + deterministic inserts together&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In the &lt;strong&gt;Commands&lt;/strong&gt; tab:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Insert in selection&lt;/strong&gt; for predictable boilerplate insertion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prompt AI to insert&lt;/strong&gt; when placement context is tricky.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;{n}&lt;/strong&gt; when you need argument shape clarity ( Inspect the command signature before insertion: it shows expected argument names/types and return details).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use &lt;strong&gt;Insert in selection&lt;/strong&gt; for predictable snippets, and switch to AI when insertion location or code adaptation depends on the surrounding context.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/003-1_HAs9F-B9l77KolI3ZZTH3g.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h3 id="40-iterate-faster-before-handoff"&gt;&lt;strong&gt;4.0: Iterate Faster Before Handoff&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Once the extensions run, enable &lt;strong&gt;Run on save&lt;/strong&gt;(from the toolbar or from the settings) while polishing labels, command categories, and small behaviour details.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;Run on save&lt;/strong&gt;, each file save triggers a reload loop for that plugin file, which makes label tweaks, command grouping changes, and message-copy iteration much faster than manual reload cycles. And with enabling the global setting (&lt;strong&gt;Load as extension on save&lt;/strong&gt;), this behaviour is present for all files by default.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/004-1_wfxS4HhzRfU5-bbKYfPonA.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h3 id="50-share-for-review"&gt;&lt;strong&gt;5.0: Share for Review&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Before packaging, you can ask a teammate to review behaviour or just share links for the extension for a quick look using:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Share a single file&lt;/strong&gt; when sharing is about one plugin file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Share a package&lt;/strong&gt; when your logic spans several files.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This keeps review lightweight: collaborators can inspect and discuss behaviour before you commit to repository structure, CI, and release wiring.&lt;/p&gt;
&lt;h3 id="60-export-as-wheel-and-validate-in-binder"&gt;&lt;strong&gt;6.0: Export as Wheel and Validate in Binder&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now test as an installable artifact in a separate environment.&lt;/p&gt;
&lt;h3 id="61-export-whl"&gt;&lt;strong&gt;6.1: Export &lt;code&gt;.whl&lt;/code&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;From the export dropdown ( in toolbar ), choose &lt;strong&gt;Export as Python package (.whl)&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="62-install-in-binder"&gt;&lt;strong&gt;6.2: Install in Binder&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open JupyterLab on Binder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upload the downloaded wheel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open a terminal and run:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;force-reinstall&lt;span class="w"&gt; &lt;/span&gt;./your_exported_package.whl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="what-this-block-does-1"&gt;What this block does:&lt;/h3&gt;
&lt;p&gt;Installs the wheel you just exported from Plugin Playground into Binder. (Uses &lt;strong&gt;— force-reinstall&lt;/strong&gt; so Binder replaces any previously installed copy with your newest build). Then refresh JupyterLab and verify commands.&lt;/p&gt;
&lt;h3 id="70-export-as-zip-and-create-a-github-repository"&gt;&lt;strong&gt;7.0: Export as Zip and Create a GitHub Repository&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;After runtime validation, move to normal engineering workflows.&lt;/p&gt;
&lt;h3 id="71-export-zip"&gt;&lt;strong&gt;7.1: Export &lt;code&gt;.zip&lt;/code&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Choose &lt;code&gt;**Export as archive (.zip)**&lt;/code&gt; from the toolbar export dropdown.&lt;/p&gt;
&lt;h3 id="72-bootstrap-repository"&gt;&lt;strong&gt;7.2: Bootstrap repository&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Unzip locally, create an empty GitHub repo, then run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Initial extension scaffold from Plugin Playground&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//github.com/&amp;lt;your-org-or-user&amp;gt;/my-jlab-extension.git&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="what-this-block-does-2"&gt;What this block does:&lt;/h3&gt;
&lt;p&gt;Initializes a new Git repository from the exported scaffold and creates an initial commit that captures the generated extension baseline.&lt;/p&gt;
&lt;p&gt;Now your prototype is in a standard repo flow with PRs, CI, and release automation.&lt;/p&gt;
&lt;h3 id="whats-next"&gt;What’s Next&lt;/h3&gt;
&lt;p&gt;Plugin Playground is still evolving. We’re actively working on the &lt;a href="https://github.com/jupyterlab/plugin-playground/pull/225"&gt;LSP integration&lt;/a&gt; for JupyterLite to bring code intelligence autocomplete and diagnostics directly into the editor, an &lt;a href="https://github.com/jupyterlab/plugin-playground/pull/231"&gt;“Ask AI” button on log errors&lt;/a&gt; that lets you send error context directly into the AI chat for instant debugging help. In the future, we would like to explore &lt;a href="https://github.com/jupyterlab/plugin-playground/issues/139"&gt;git integration&lt;/a&gt; to snapshots every successful load, paired with a &lt;a href="https://github.com/jupyterlab/plugin-playground/issues/140"&gt;built-in diff viewer&lt;/a&gt; to compare any snapshot against your current file. If you have any suggestions or encounter any problems, please let us know by opening &lt;a href="https://github.com/jupyterlab/plugin-playground/issues"&gt;an issue on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="acknowledgements"&gt;&lt;strong&gt;Acknowledgements&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We are grateful to the &lt;a href="https://jupyterfoundation.org/join/"&gt;Jupyter Foundation&lt;/a&gt; and its &lt;a href="https://jupyterfoundation.org/members/"&gt;members&lt;/a&gt; for sponsoring the development of Plugin Playground as part of &lt;a href="https://jupyterfoundation.org/community-funding-proposals/"&gt;Community Funding Proposals 2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We thank Jeremy Tuloup, Nick Bollweg, and Nicolas Brichet for providing inspiration, review, and advice across our work on the plugin and its dependencies. Finally, this work would not be possible without the &lt;a href="https://github.com/jupyterlab/plugin-playground/graphs/contributors?from=01%2F01%2F2020&amp;amp;to=18%2F12%2F2024"&gt;authors of earlier&lt;/a&gt; iterations of Plugin Playground.&lt;/p&gt;
&lt;h3 id="about-the-developers"&gt;&lt;strong&gt;About the&lt;/strong&gt; Developers&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/anuj-singh-2b2ab6206/"&gt;&lt;strong&gt;Anuj Singh&lt;/strong&gt;&lt;/a&gt; is an OSS intern at OpenTeams. Anuj implemented Plugin Playground v1.0.0 and contributed to its dependencies during his internship at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/smera-goel/"&gt;&lt;strong&gt;Smera Goel&lt;/strong&gt;&lt;/a&gt; is a UI/UX designer at &lt;a href="https://quansight.com/"&gt;Quansight&lt;/a&gt;. Smera contributed to the user experience and design direction of Plugin Playground.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/michal-krassowski/"&gt;&lt;strong&gt;Michał Krassowski&lt;/strong&gt;&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;. Mike provided guidance and direction for the development of the Plugin Playground.&lt;/p&gt;
&lt;h3 id="further-reading"&gt;Further reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Blog post on &lt;a href="https://openteams.com/plugin-playground-ai-integration/"&gt;how and why we integrated AI functions in the playground&lt;/a&gt; extension&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyterlab/plugin-playground/blob/main/CHANGELOG.md"&gt;Changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyterlab/plugin-playground/blob/main/README.md"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="AI"/><category term="extensions"/><category term="JupyterLab"/><category term="JupyterLite"/></entry><entry><title>700 JupyterLab 4 Extensions!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/" rel="alternate"/><published>2026-03-13T17:18:00+00:00</published><updated>2026-03-13T17:18:00+00:00</updated><author><name>Konstantin Taletskiy</name></author><id>tag:jasongrout.github.io,2026-03-13:/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/</id><summary type="html">&lt;p&gt;The JupyterLab extension ecosystem just crossed 700 extensions compatible with JupyterLab 4!&lt;/p&gt;
</summary><content type="html">&lt;figure&gt;
&lt;img alt="The number 700 formed by a mosaic of JupyterLab extension icons and author avatars" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/001-1_z7SyUie14-caE28XRvtH9A.jpg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;700 extensions for JupyterLab 4, and counting!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The JupyterLab extension ecosystem just crossed &lt;strong&gt;700 extensions compatible with JupyterLab 4!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s 700 community-built plugins — from astronomical data viewers to reactive notebooks, from genome browsers to workflow managers — created by hundreds of developers, research labs, and companies around the world.&lt;/p&gt;
&lt;h2 id="what-are-jupyterlab-extensions"&gt;What Are JupyterLab Extensions?&lt;/h2&gt;
&lt;p&gt;Extensions are how JupyterLab becomes a Git client, a dashboard builder, a genomics viewer, or an AI workspace — without changing the core application. Install one with &lt;code&gt;pip install&lt;/code&gt;, and it activates automatically.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Collage of screenshots showing popular JupyterLab extensions in action: jupytext with file format options, jupyterlab-h5web visualizing HDF5 data, jupyter-collaboration for real-time editing, jupyterlab-git with diff and staging views, ipywidgets with interactive parameter sliders, ipympl for inline matplotlib plots, jupytergis-lab for geospatial data, sidecar for side-panel output, and jupyterlab-latex for document preview." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/002-1_TYJMNY9zzWcawsQsOkPuLQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Popular JupyterLab extensions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This is by design: JupyterLab itself is built as a collection of extensions — the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/filebrowser"&gt;file browser&lt;/a&gt;, the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/notebook"&gt;notebook editor&lt;/a&gt;, the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/terminal"&gt;terminal&lt;/a&gt; are all plugins. The same architecture that powers the core lets the community build what they need. For background, see &lt;a href="/posts/2019/99-ways-to-extend-the-jupyter-ecosystem/"&gt;99 ways to extend the Jupyter ecosystem&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-ecosystem-at-700"&gt;The Ecosystem at 700&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;700+ extensions compatible with JupyterLab 4&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~960 total extensions&lt;/strong&gt; published on PyPI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~9.8 million downloads/month&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100M+ total downloads&lt;/strong&gt; in the past year&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By any measure, a substantial software layer has grown around JupyterLab.&lt;/p&gt;
&lt;h2 id="how-we-got-here"&gt;How We Got Here&lt;/h2&gt;
&lt;p&gt;The ecosystem crossed &lt;strong&gt;600 JL4-compatible extensions in late October 2025&lt;/strong&gt;, days before &lt;a href="https://www.jupytercon.com/"&gt;JupyterCon in San Diego&lt;/a&gt;. At the conference, we ran a full-day &lt;a href="https://jupytercon.github.io/jupytercon2025-developingextensions/"&gt;Extension Development for Everyone&lt;/a&gt; tutorial with hands-on rapid prototyping. By early March 2026, we hit &lt;strong&gt;700&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The ecosystem has been growing at a steady pace, averaging about 18 new extensions per month, with November 2025 setting an all-time monthly record of 33. Modern tooling is helping: better templates, documentation, and code generation tools have lowered the bar for what once required deep familiarity with TypeScript, Lumino, and JupyterLab internals.&lt;/p&gt;
&lt;h2 id="where-the-extensions-are"&gt;Where the Extensions Are&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Bar chart showing number of JupyterLab extensions by category. Development and Version Control leads with 267, followed by Cloud and Platform Integration (127), AI and Code Assistance (88), Specialized Computing (83), Visualization and Dashboards (80), Educational and Grading (70), Theme (67), System and Resource Management (65), Runtime and Kernel Extensions (44), Workflow and Automation (37), and Data and Database Integration (26)" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/003-1_lijkfmv6G5n96sO2vRzB5Q.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Number of JupyterLab extensions by category&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Bar chart showing 30-day PyPI downloads per category. Development and Version Control leads at 5.4 million, followed by Visualization and Dashboards (2.7M), System and Resource Management (602K), AI and Code Assistance (253K), Runtime and Kernel Extensions (206K), Cloud and Platform Integration (178K), Data and Database Integration (148K), Educational and Grading (99K), Workflow and Automation (69K), Specialized Computing (56K), and Theme (48K)" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/004-1_wW76DvGMkq2l_Xvr0JO_vw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Monthly PyPI downloads by category&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Development &amp;amp; Version Control dominates both in count (267) and downloads (5.4M/month). Visualization &amp;amp; Dashboards (2.7M/month) and System &amp;amp; Resource Management (602K/month) round out the top three most downloaded categories. But the fastest-growing categories point to where things are heading. Here’s what’s new in 2026:&lt;/p&gt;
&lt;h2 id="jupyterlabs-ai-layer-starts-taking-shape"&gt;JupyterLab’s AI Layer Starts Taking Shape&lt;/h2&gt;
&lt;p&gt;AI isn’t yet the biggest category in JupyterLab, but it may be the clearest signal of where new interaction patterns are emerging:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-ai-acp-client?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyter-ai-acp-client&lt;/strong&gt;&lt;/a&gt; — Brings external AI agents into JupyterLab’s chat via the Agent Communication Protocol. Ships with Claude Code and Kiro personas.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/nb-margin?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;nb-margin&lt;/strong&gt;&lt;/a&gt; — Annotate cells with margin comments, and Claude Code edits them. A different paradigm from chat-based AI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlite-ai-kernels?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyterlite-ai-kernels&lt;/strong&gt;&lt;/a&gt; — AI-powered kernels for JupyterLite, from Jeremy Tuloup. AI-assisted computation entirely in the browser.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-chat-components?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyter-chat-components&lt;/strong&gt;&lt;/a&gt; — Reusable chat UI components from Project Jupyter — building blocks for the next generation of AI tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These extensions reflect what the JupyterLab team identified as a 2026 priority: first-class integration with AI tooling.&lt;/p&gt;
&lt;h2 id="reproducibility-gets-a-toolchain"&gt;Reproducibility Gets a Toolchain&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://labextensions.dev/extensions/calkit-python?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;calkit-python&lt;/strong&gt;&lt;/a&gt; is the most downloaded new extension of 2026 (11,000+ monthly downloads). It gives notebooks project-scoped environments, graphical package management via Astral’s &lt;code&gt;uv&lt;/code&gt;, and one-click notebook pipelines with freshness tracking. Think “Makefiles for notebooks” meets “Poetry for Jupyter.”&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Calkit extension in JupyterLab showing a notebook pipeline with three stages — collect-data, process-data, and plot-results — in the left sidebar, alongside a Python notebook with pandas code. The toolbar shows environment and pipeline stage indicators" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/005-0_vKMU4QiLD406Ac9c.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Calkit manages notebook pipelines with environment tracking and one-click reruns. The orange ‘run’ button signals stale outputs that need to be regenerated.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-projspec?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyter-projspec&lt;/strong&gt;&lt;/a&gt; (from the fsspec contributors) takes a complementary approach — it brings &lt;a href="https://github.com/fsspec/projspec"&gt;projspec&lt;/a&gt; into JupyterLab, letting you scan and analyze project structures directly from the notebook environment.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jupyter-projspec extension in JupyterLab showing the Project Spec sidebar panel with detected project types including Git Repository, Pixi, and Poetry, alongside the file browser and JupyterLab launcher with notebook and console options." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/006-0_GSE92ExuZWmjQHtA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jupyter-projspec integrates to system filebrowser to show the project metadata&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="science"&gt;Science&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/fitsview?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;fitsview&lt;/strong&gt;&lt;/a&gt; — Stream FITS astronomical data slices directly in JupyterLab without downloading full files.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-urdf-test?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyterlab-urdf-test&lt;/strong&gt;&lt;/a&gt; — 3D robot model viewer/editor (URDF + Three.js), from &lt;a href="https://github.com/jupyter-robotics"&gt;jupyter-robotics&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/climb-jupyter-igv?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;climb-jupyter-igv&lt;/strong&gt;&lt;/a&gt;— Integrative Genomics Viewer with S3 access for bioinformatics.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/ggblab?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;ggblab&lt;/strong&gt;&lt;/a&gt;— GeoGebra interactive geometry with bidirectional Python communication. Second most downloaded new extension of 2026.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="accessibility"&gt;Accessibility&lt;/h2&gt;
&lt;p&gt;Accessibility has been a growing focus for JupyterLab core and extensions are starting to address it too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-a11y-checker?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyterlab-a11y-checker&lt;/strong&gt;&lt;/a&gt;— From UC Berkeley’s &lt;a href="https://github.com/berkeley-dsep-infra/jupyterlab-a11y-checker"&gt;DSEP infrastructure team&lt;/a&gt;, this extension scans notebooks for WCAG 2.1 AA issues: missing alt text, heading structure, table headers, color contrast, and link text. Guided fix interfaces, optional AI suggestions, and a CLI for CI pipelines. Over 11,000 total downloads and a &lt;a href="https://a11y-checker-guide.datahub.berkeley.edu/"&gt;documentation site&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-change-ui-font-size-fix?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyterlab-change-ui-font-size-fix&lt;/strong&gt;&lt;/a&gt; — Fixes file browser icon misalignment when users change the UI font size — a small but real pain point for anyone who needs larger text.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="27-extensions-one-platform"&gt;27 Extensions, One Platform&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/stellarshenson/stellars-jupyterlab-ds"&gt;Stellars&lt;/a&gt; is a JupyterLab-based data science platform — GPU support, MLflow, TensorBoard, Optuna — assembled from &lt;strong&gt;27 custom extensions&lt;/strong&gt; covering everything from &lt;a href="https://labextensions.dev/extensions/jupyterlab-branding-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;branding&lt;/a&gt; and &lt;a href="https://labextensions.dev/extensions/jupyterlab-vscode-icons-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;file icons&lt;/a&gt; to &lt;a href="https://labextensions.dev/extensions/jupyterlab-kernel-terminal-workspace-culler-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;kernel management&lt;/a&gt; and &lt;a href="https://labextensions.dev/extensions/jupyterlab-drawio-render-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;diagram rendering&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-vscode-icons-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;VS Code file icons&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-trash-mgmt-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;trash management&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-mmd-to-png-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;Mermaid-to-PNG conversion&lt;/a&gt;, and more.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;JupyterLab is now flexible enough that one developer can assemble a domain-specific product entirely from extension building blocks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="want-to-build-your-own"&gt;Want to Build Your Own?&lt;/h2&gt;
&lt;p&gt;The JupyterCon tutorial is fully available: &lt;a href="https://jupytercon.github.io/jupytercon2025-developingextensions/"&gt;step-by-step materials&lt;/a&gt; and the complete &lt;a href="https://www.youtube.com/watch?v=z-KZ6CjZjbM"&gt;YouTube recording&lt;/a&gt;. It covers scaffolding, plugin architecture, publishing to PyPI, and rapid prototyping techniques. The tools have never been more accessible.&lt;/p&gt;
&lt;h2 id="how-we-track-this"&gt;How We Track This&lt;/h2&gt;
&lt;p&gt;The data behind this post comes from the &lt;a href="https://labextensions.dev"&gt;JupyterLab Marketplace&lt;/a&gt;, a community &lt;a href="https://github.com/orbrx/jupyter-marketplace"&gt;project&lt;/a&gt; that tracks all published JupyterLab extensions using PyPI data. The marketplace refreshes automatically and provides download trends, category breakdowns, and discovery tools.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab Marketplace homepage showing a grid of popular extensions with download counts and GitHub stars — including jupyter-archive, jupyter-resource-usage, ipyanchorviz, jupyterlab-execute, jupyter-collaboration, jupysql-plugin, jupyterlab-unfold, jupyter-ai, jupyterlab-code-snippets, and jupyterlab-autoscroll." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/007-1_CkZpwkmEKNiXm_XsiBRACQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab Marketplace&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For more on the data and methodology, see our &lt;a href="https://www.youtube.com/watch?v=OWt3Yzhrs1E"&gt;PyData Boston 2025 talk&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s Next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;New interaction patterns&lt;/strong&gt; are still being figured out — chat-based assistance, cell annotations, agent protocols. Probably all of them for different use cases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducibility tooling&lt;/strong&gt; suggests the community is ready for opinionated workflow management built into the notebook experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ensuring extensions keep working as JupyterLab evolves is critical — the team has been &lt;a href="https://github.com/jupyterlab/frontends-team-compass/issues/301"&gt;discussing extension compatibility testing&lt;/a&gt; at recent contributors calls.&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://labextensions.dev"&gt;Marketplace&lt;/a&gt; itself, we’re working on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deeper integration with JupyterLab Extension Manager&lt;/strong&gt; — deep links and “Install in JupyterLab” buttons to go from discovery to installation in one click.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expanding Trove classifiers&lt;/strong&gt; to indicate Jupyter Notebook and JupyterLite support. All three use the same extension system, with important caveats: Notebook extensions need to target different UI elements, and JupyterLite extensions cannot have a server component.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better contribution signals&lt;/strong&gt; — surfacing commits, PRs, and issues to help users gauge how actively maintained an extension is.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;At 700 extensions, the community now shapes JupyterLab as much as the core team does. If you’re building extensions, thank you! Every one of them makes Jupyter better for someone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="about-the-author"&gt;About the author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/taletskiy/"&gt;Konstantin Taletskiy&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://www.anaconda.com/"&gt;Anaconda&lt;/a&gt; working on open-source Jupyter. He is a contributor to JupyterLab, maintainer of &lt;a href="https://github.com/mamba-org/mamba-gator"&gt;mamba-gator&lt;/a&gt; and &lt;a href="https://github.com/jupyterlab/jupyterlab-latex"&gt;jupyterlab-latex&lt;/a&gt;, and the creator of the &lt;a href="https://labextensions.dev"&gt;JupyterLab Marketplace&lt;/a&gt;.&lt;/p&gt;
</content><category term="community"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>A theme editor for JupyterLab</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/" rel="alternate"/><published>2023-06-19T08:11:00+00:00</published><updated>2023-06-19T08:11:00+00:00</updated><author><name>Florence Haudin</name></author><id>tag:jasongrout.github.io,2023-06-19:/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/</id><summary type="html">&lt;p&gt;JupyterLab is a comprehensive web user interface for scientific and technical computing providing tools such as notebooks, text editors…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;JupyterLab is a comprehensive web user interface for scientific and technical computing providing tools such as &lt;a href="https://jupyterlab.readthedocs.io/en/stable/user/notebook.html#notebook"&gt;notebooks&lt;/a&gt;, text editors, consoles, and terminals. Like many similar tools, it allows users to choose from light or dark color themes. However, users may want to further adjust the looks of the interface, just for fun or for specific personal preferences or needs.&lt;/p&gt;
&lt;p&gt;The ability to fine-tune contrast, color palettes, and fonts can be very useful for accessibility, an essential requirement for software to be usable by the whole community. There is an ongoing &lt;a href="https://jupyter-accessibility.readthedocs.io/"&gt;project-wide effort&lt;/a&gt; to improve accessibility in Jupyter. Choices of color palettes impact color-blind users while font choices can have a significant impact on people affected by dyslexia, or who suffer from migraines.&lt;/p&gt;
&lt;p&gt;This diversity of requirements and preferences shows that end users should have the means to adjust parameters and tweak existing themes. Subtle differences can improve comfort significantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JupyterLab themes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Theming in JupyterLab is enabled by the fact that most of the layout and colors of the UI are defined by a set of CSS variables. There is no need for a complete stylesheet: one can set values for the base parameters from which the appearance of the user interface is derived. This system ensures a consistent look and feel throughout the application.&lt;/p&gt;
&lt;p&gt;Custom themes provide a set of values for the base CSS variables and package the resulting CSS file in JupyterLab extensions. &lt;a href="https://github.com/search?q=jupyter+lab+theme"&gt;A search on GitHub&lt;/a&gt; returns 29 repositories defining such custom themes for JupyterLab.&lt;/p&gt;
&lt;p&gt;Creating a theme requires implementing values for &lt;em&gt;dozens&lt;/em&gt; of base CSS variables. This is often achieved by theme authors tweaking the values given in the default dark or light themes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Generating a consistent palette, testing, and packaging the outcome in the form of a JupyterLab extension requires development skills and is neither direct nor trivial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;A new tool for authoring JupyterLab themes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To lower the bar for customizing JupyterLab we created a new tool providing a simple interface for tuning the JupyterLab appearance interactively, allowing theme authors to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use the selected colors in their application and “pin down” the result in the configuration,&lt;/li&gt;
&lt;li&gt;export the outcome in a form amenable to packaging into a new JupyterLab theme.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The theme editor extension is a theme prototyping tool, a sandbox to test changes in colors, font family and font size, and a configuration tool for end users. It displays a reduced set of parameters one can play with, allowing users to select:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;colors through color pickers,&lt;/li&gt;
&lt;li&gt;numerical values from sliders (for font size, border radius and width),&lt;/li&gt;
&lt;li&gt;font families from a predefined dropdown list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A screenshot of JupyterLab with the theme editor panel is displayed in Figure 1, with a notebook opened in light theme. Figure 2 shows different screenshots with different custom themes.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Screenshot of JupyterLab interface with the theme editor on the left panel and a notebook opened. The theme chosen is the light default one." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/images/001-0_vj7tjopuS2GvZg3N.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 1: The theme editor extension in JupyterLab.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="4 screenshots of the JupyterLab interface are shown to illustrate the kinds of themes that can be generated by the theme editor, with gray, blue, orange and pink tones and different font families." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/images/002-0_5OLBiA8tmTbD1Nan.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 2: Examples of dynamically editing the color and fonts scheme of JupyterLab with the theme editor.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Color palettes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For all the colors (accent, brand, border, error, info, layout, success, warn), the same logic is applied. The user picks a base color and a palette is automatically calculated using Microsoft’s &lt;a href="https://www.fast.design/docs/api/fast-colors.colorpalette"&gt;fast-colors&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;Let’s take the specific example of the layout colors impacting the background of most elements of the interface. They are defined using &lt;em&gt;— jp-layout-colori&lt;/em&gt; CSS variables from white to light grays (for the light theme).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;white&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;white&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-md-grey-200&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-md-grey-400&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-md-grey-600&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The number of colors in the palette fits the number of colors in the CSS set of variables (here the palette contains 5 colors). There are different ways to define colors (hexadecimal, RGBA, HSLA). The latter format has the advantage of giving direct access to luminance &lt;em&gt;L&lt;/em&gt;, a measure of the lightness of a given color.&lt;/p&gt;
&lt;p&gt;An inverse layout palette is computed for use with most text elements. The luminance of the inverse layout color &lt;em&gt;L’&lt;/em&gt; is calculated as &lt;em&gt;1-L&lt;/em&gt; plus a correction depending on how &lt;em&gt;1-L&lt;/em&gt; is close to 0.5. There isn’t a general CSS rule in JupyterLab concerning elements on top of a background but some PRs were proposed to reinforce the coupling between inverse layout elements and layout backgrounds with the same color index. The contrast still needs to be improved though, by using other palettes or better corrections when defining the inverse layout colors.&lt;/p&gt;
&lt;p&gt;The full process just described from picking a layout color to resulting palettes and the corresponding interface is illustrated in Figure 3.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The figure is divided into 3 parts. Part 1 is a screenshot with color picker with a green color selected. Part 2 is a capture showing the resulting palette calculated from this color choice. Finally, part 3 is a screenshot of JupyterLab interface with the different green tones calculated from the base color that has been picked." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/images/003-0_vD9AcycD3qSCAJgu.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 3: From a color to the layout, and inverse layout palettes with the resulting interface.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Font size and font family&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As mentioned previously, font size and font family can be tuned too. Let’s take the example of the &lt;em&gt;— jp-ui-font-size[i]&lt;/em&gt; CSS variables defining the text size of most of the text elements in the interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-scale-factor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;2&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-size0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;8333em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-size1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;13px&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;/* Base font size */&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-size2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;2em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-size3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;44em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A slider lets the user control the base font size associated with &lt;em&gt;— jp-ui-font-size1&lt;/em&gt;. The other font sizes are then calculated by applying a scale factor to the base font.&lt;/p&gt;
&lt;p&gt;Concerning font families, a non-exhaustive list is proposed (i.e., default browser fonts or Google fonts). It includes both standard accessible fonts (Arial, Courier, Helvetica, Sans Serif, system-ui, Times New Roman, Verdana) and less usual ones to create artistic or special interfaces with cursive fonts ( for instance Dancing Script or Single Day). Only 2 fonts are proposed for code: Space Mono and monospace.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Storing the new theme&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once satisfied with a new theme, users can export it using a button at the top of the theme editor panel. This creates a &lt;em&gt;variable.css&lt;/em&gt; file that can be packaged in a theme extension (see the &lt;a href="https://github.com/jupyterlab/extension-cookiecutter-ts/"&gt;extension template&lt;/a&gt;). The new parameters are also synchronously saved in the settings editor and can be restored for the next JupyterLab opening if the boolean &lt;em&gt;useSettings&lt;/em&gt; is set to true. If not, the interface will look like in Figure 1: with light theme default CSS values. Resetting &lt;em&gt;useSettings&lt;/em&gt; to true will restore the formerly tuned parameters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Future development&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The look and feel of the theme editor UI still needs to be improved. We will work on unifying the styling of all interfaces making use of &lt;a href="https://github.com/rjsf-team/react-jsonschema-form"&gt;react-jsonschema-form&lt;/a&gt;. It is already used for the JupyterLab settings editor, the notebook metadata editor of JupyterLab 4.0, and several extensions.&lt;/p&gt;
&lt;p&gt;Moreover, the current implementation is bound to fast-colors palettes and we may want to use a different approach, like decoupling the layout and inverse layout palettes to reach better contrasts or give more freedom in the color choices.&lt;/p&gt;
&lt;p&gt;Any help (e.g., filling issues for bugs or enhancement requests, opening pull requests) to improve &lt;a href="https://github.com/jupyterlab-contrib/jupyterlab-theme-editor"&gt;the extension&lt;/a&gt; is welcome.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try it out&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can install the &lt;a href="https://pypi.org/project/jupyter-theme-editor"&gt;PyPI package&lt;/a&gt; by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jupyter_theme_editor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can also try the theme editor on &lt;a href="https://mybinder.org/v2/gh/jupyterlab-contrib/jupyterlab-theme-editor/main?urlpath=lab"&gt;Binder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aknowledgement&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Many thanks to &lt;a href="https://twitter.com/ihuicatls"&gt;Isabel Paredes&lt;/a&gt; for drawing the palette icon!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;About the authors&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Florence Haudin is a scientific software developer at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt;. She contributes to JupyterLab and to ipyleaflet.&lt;/p&gt;
&lt;p&gt;Frédéric Collonval supervised this work as a technical director at QuantStack. He is a member of the JupyterLab core team and authored several JupyterLab extensions.&lt;/p&gt;
</content><category term="accessibility"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>Packaging for Jupyter in 2022</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2022/packaging-for-jupyter-in-2022/" rel="alternate"/><published>2022-09-06T15:15:00+00:00</published><updated>2022-09-06T18:56:00+00:00</updated><author><name>Steven Silvester</name></author><id>tag:jasongrout.github.io,2022-09-06:/medium-archive/pelican/posts/2022/packaging-for-jupyter-in-2022/</id><summary type="html">&lt;p&gt;Packaging has been difficult and changing over time in the Python ecosystem, and packaging extensions for Jupyter has been especially…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Packaging has been difficult and changing over time in the Python ecosystem, and packaging extensions for Jupyter has been especially troublesome. Jupyter extensions typically have a build step to handle JavaScript assets, and provide Jupyter configuration and data using the data_files capability in Setuptools. If you’ve ever developed your own Jupyter extension before, you understand the pain all too well.&lt;/p&gt;
&lt;p&gt;We’ve tried to help over the years. We created the &lt;a href="https://github.com/jupyter/jupyter-packaging"&gt;Jupyter Packaging&lt;/a&gt; library, with helper functions and Setuptools builder classes. We have also created cookiecutter repositories for &lt;a href="https://github.com/jupyterlab/extension-cookiecutter-ts"&gt;JupyterLab&lt;/a&gt;, &lt;a href="https://github.com/jupyter-widgets/widget-ts-cookiecutter"&gt;Widget&lt;/a&gt;, and &lt;a href="https://github.com/jupyter-server/extension-cookiecutter"&gt;Server&lt;/a&gt; extensions. Additionally, there have been a number of libraries and Python Enhancement Proposals produced by the &lt;a href="https://www.pypa.io/en/latest/index.html"&gt;Python Packaging Authority&lt;/a&gt;, aiming to standardize and improve the Python Packaging ecosystem. Even with all these improvements, packaging Jupyter libraries remained a challenge.&lt;/p&gt;
&lt;p&gt;Then we discovered the &lt;a href="https://hatch.pypa.io/latest/"&gt;Hatch&lt;/a&gt; project.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Logo for the Hatch Python Project" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/packaging-for-jupyter-in-2022/images/001-1_SL9lt02en8mWEJylo9sFhw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;“Hatch is a modern, extensible Python project manager.”&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Hatch is a standards-based Python packaging backend that is flexible and extensible. Hatch came with many of the things we needed. It provides a supported replacement for Setuptool’s (deprecated) data_files called &lt;a href="https://hatch.pypa.io/latest/plugins/builder/wheel/#options"&gt;&lt;em&gt;shared-data&lt;/em&gt;&lt;/a&gt;. We could easily extend Hatch to handle the JavaScript build step required for most of our applications and extensions with a new plugin called &lt;a href="https://github.com/jupyterlab/hatch-jupyter-builder"&gt;Hatch Jupyter Builder&lt;/a&gt; that is tailored for Jupyter extensions. The best news? No more complicated &lt;em&gt;setup.py&lt;/em&gt; files in your package. Just a single, standards-driven &lt;em&gt;pyproject.toml&lt;/em&gt; in your root directory describing all of your package details.&lt;/p&gt;
&lt;p&gt;We believe this is a major improvement for Jupyter developers and extension authors.&lt;/p&gt;
&lt;p&gt;For Jupyter extension authors that previously used one of our cookiecutter templates or Jupyter Packaging directly, we offer a &lt;a href="https://hatch-jupyter-builder.readthedocs.io/en/stable/source/how_to_guides/migrating_javascript_projects.html"&gt;migration&lt;/a&gt; script and guides to convert the Setuptools and Jupyter Packaging configuration to use Hatch and Hatch Jupyter Builder. We also provide a script that will compare the files in an &lt;em&gt;sdist&lt;/em&gt; or &lt;em&gt;wheel&lt;/em&gt; before and after migration to make sure you do not accidentally add or subtract files.&lt;/p&gt;
&lt;p&gt;We have already been migrating many of the core &lt;a href="https://github.com/jupyter-server/jupyter_server/pull/845"&gt;Jupyter&lt;/a&gt; and &lt;a href="https://github.com/ipython/ipykernel/pull/931"&gt;IPython&lt;/a&gt; libraries to use the Hatch build backend, using Hatch Jupyter Builder where appropriate. We have upgraded several of our cookiecutter templates to use Hatch as well. The transition has been quite smooth!&lt;/p&gt;
&lt;p&gt;We highly encourage you to try out Hatch and Hatch Jupyter Builder for existing or new Python libraries.&lt;/p&gt;
&lt;h2 id="about-the-authors"&gt;About the authors&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Steven Silvester&lt;/strong&gt; is a Senior Python Engineer at MongoDB working on the Database Experience Team. He is a Jupyter Steering Council Member and contributes to the maintenance of many of the Jupyter projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zach Sailer&lt;/strong&gt; is a Senior Software Engineer at Apple, where he is the Jupyter Open Source Champion. He is a Jupyter Distinguished Contributor and leader of the Jupyter Server subproject.&lt;/p&gt;
</content><category term="extensions"/></entry><entry><title>Visual programming in JupyterLab with Blockly</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2022/visual-programming-in-jupyterlab-with-blockly/" rel="alternate"/><published>2022-07-29T13:12:00+00:00</published><updated>2022-07-29T13:12:00+00:00</updated><author><name>Denisa Checiu</name></author><id>tag:jasongrout.github.io,2022-07-29:/medium-archive/pelican/posts/2022/visual-programming-in-jupyterlab-with-blockly/</id><summary type="html">&lt;p&gt;Block-based programming has become ubiquitous in school curricula for early computer science education. It is an excellent means to teach…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Block-based programming has become ubiquitous in school curricula for early computer science education. It is an excellent means to teach basic programming concepts without having to deal with the specific syntax of a text-based programming language.&lt;/p&gt;
&lt;p&gt;When moving from block-based programming to a more classical language like Python, students often have to switch to a completely new environment. In order to provide a smooth ramp of complexity for learners, we designed a JupyterLab extension for Blockly so that Jupyter can be used from the very first steps of their learning journey. We also used Blockly’s code generation feature to create a more integrated experience with Jupyter.&lt;/p&gt;
&lt;h2 id="but-what-exactly-is-blockly"&gt;&lt;strong&gt;But what exactly is Blockly?&lt;/strong&gt;&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Screenshot of the JupyterLab-Blockly extension, running a simple code" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/visual-programming-in-jupyterlab-with-blockly/images/001-0_yEQ8djozBKDLcCxZ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The JupyterLab-Blockly extension, running a simple loop, and the corresponding Python code in a cell.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://developers.google.com/blockly"&gt;Blockly&lt;/a&gt; is an open source library designed by &lt;a href="https://twitter.com/GoogleOSS"&gt;Google&lt;/a&gt; to make coding easier and more accessible through block-based visual programming. It uses interlocking graphical blocks to represent coding concepts, while giving the user full creative freedom, it removes all language specific syntax requirements.&lt;/p&gt;
&lt;h2 id="getting-started-installing-the-blockly-extension"&gt;&lt;strong&gt;Getting started: installing the Blockly extension&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;To install the extension, you just need to execute the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mamba install -c conda-forge jupyterlab-blockly
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once installed, you can easily open a Blockly editor directly from the launcher, and start experimenting!&lt;/p&gt;
&lt;h2 id="features"&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The UI is made primarily of the toolbox located on the left side and the canvas where you can drag and drop the blocks you want to use in your code. When you are ready, just click the run button and see how the syntactically correct converted code appears on the bottom part of your screen in the programming language of your choice along with the output!&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Short demo on how to use the JupyterLab-Blockly extension" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/visual-programming-in-jupyterlab-with-blockly/images/002-0_69P-HcYWrtriGmS-.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Short demo for the JupyterLab-Blockly extension&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The extension is also prepared to adapt to all your needs. The workspace is ready to generate the blocks using your selected JupyterLab kernel and modify its colors based on your individual theme, whether it’s dark, light or personalized.&lt;/p&gt;
&lt;p&gt;JupyterLab-Blockly integrates smoothly with the rest of the Jupyter stack: by using Jupyter kernels to execute the generated code, by reusing the JupyterLab code cell component to display the generated code, etc.&lt;/p&gt;
&lt;p&gt;In doing so, JupyterLab-Blockly remains consistent with the rest of the UI (consistent syntax highlighting, theming, and life cycle of Jupyter kernels).&lt;/p&gt;
&lt;h2 id="robotics-education"&gt;&lt;strong&gt;Robotics education&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;On top of JupyterLab-Blockly, we also built another extension dedicated to operating robots with the help of visual programming — more specifically, we created a toolbox which includes the blocks necessary to control the &lt;a href="https://niryo.com/fr/product/niryo-one/"&gt;Niryo One&lt;/a&gt; robot. Using ROS can be a time consuming task, but the pre-built visual blocks make it easier to experiment and faster to produce a prototype for any project you have in mind.&lt;/p&gt;
&lt;p&gt;To install this extension you need to execute the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mamba install -c conda-forge jupyterlab-niryo-one
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, all you need to do is switch from the &lt;em&gt;‘default’&lt;/em&gt; toolbox to the &lt;em&gt;‘niryo’&lt;/em&gt; one, and you will immediately gain access to forty-two new blocks, so if you have access to one of these robots, give it a try!&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Demo on how to use the JupyterLab-NiryoOne extension" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/visual-programming-in-jupyterlab-with-blockly/images/003-0_KSNrd_DXpVWHkvPC.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Short demo for the JupyterLab-NiryoOne extension&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Both extensions can be found on GitHub. The &lt;a href="https://github.com/QuantStack/jupyterlab-blockly"&gt;JupyterLab-Blockly&lt;/a&gt; extension is ready to be used as a base for other projects; you can register new blocks, toolboxes and generators. Taking the &lt;a href="https://github.com/QuantStack/jupyterlab_niryo_one"&gt;JupyterLab-Niryo-One&lt;/a&gt; extension as an example, feel free to experiment on your own!&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src="https://www.youtube-nocookie.com/embed/41GoTIljCug" title="Demo JupyterLab-Niryo-One with Blockly" width="560" height="315" style="aspect-ratio: 560 / 315" loading="lazy" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;figcaption&gt;
&lt;p&gt;Controlling the Niryo-one robot with the JupyterLab-blockly extension.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can also try out the JupyterLab-Blockly extension now, directly from your browser, using JupyterLite! Just click on the &lt;a href="https://jupyterlab-blockly.readthedocs.io/en/latest/lite/lab/index.html?path=example.jpblockly"&gt;link&lt;/a&gt; below.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://jupyterlab-blockly.readthedocs.io/en/latest/lite/lab/index.html?path=example.jpblockly"&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/visual-programming-in-jupyterlab-with-blockly/images/004-0_Z2TP8VlU-j14Taxo.webp" alt="Link to JupyterLite for the JupyterLab-Blockly extension" loading="lazy" data-body-image=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="about-the-authors"&gt;&lt;strong&gt;About the authors&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://twitter.com/DenisaCheciu"&gt;&lt;strong&gt;Denisa Checiu&lt;/strong&gt;&lt;/a&gt; is a Robotics working student at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt;. She is also currently pursuing her bachelor’s degree, majoring in Robotics and Intelligent Systems and minoring in Computer Science.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/carlosherrerob"&gt;&lt;strong&gt;Carlos Herrero&lt;/strong&gt;&lt;/a&gt; is a scientific software developer at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt; passionate about AI and its applications to robotics. His current work focuses on the JupyterLab stack and expanding the &lt;a href="https://jupyter.org/"&gt;Jupyter ecosystem&lt;/a&gt; by developing new extensions to integrate &lt;a href="https://www.ros.org/"&gt;ROS&lt;/a&gt; in JupyterLab and also contributing to &lt;a href="https://github.com/voila-dashboards/voila"&gt;Voilà&lt;/a&gt;.&lt;/p&gt;
</content><category term="extensions"/><category term="JupyterLab"/></entry></feed>