<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jupyter Blog - Darshan Kr. Paudyal</title><link href="https://jasongrout.github.io/medium-archive/pelican/" rel="alternate"/><link href="https://jasongrout.github.io/medium-archive/pelican/feeds/author-darshan-kr-paudyal.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>Catching Jupyter-specific bugs before CI does: announcing jupyter eslint plugin</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/catching-jupyter-specific-bugs-before-ci-does/" rel="alternate"/><published>2026-06-11T15:13:00+00:00</published><updated>2026-06-11T15:13:00+00:00</updated><author><name>Darshan Kr. Paudyal</name></author><id>tag:jasongrout.github.io,2026-06-11:/medium-archive/pelican/posts/2026/catching-jupyter-specific-bugs-before-ci-does/</id><summary type="html">&lt;p&gt;We’re excited to announce the release of the first version of the Jupyter ESLint plugin @jupyter/eslint-plugin, now available on npm. It’s…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/catching-jupyter-specific-bugs-before-ci-does/images/001-1_CXmGuOa5O6Z60l4KZSEo3g.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;We’re excited to announce the release of the first version of the Jupyter ESLint plugin &lt;a href="https://www.npmjs.com/package/@jupyter/eslint-plugin"&gt;&lt;code&gt;@jupyter/eslint-plugin&lt;/code&gt;&lt;/a&gt;, now available on npm. It’s a custom ESLint plugin designed specifically for the official Jupyter frontends (JupyterLab, Notebook, JupyterLite) and TypeScript extensions built around them.&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;If you’ve ever written a JupyterLab extension, you’ve probably hit something like this:&lt;/p&gt;
&lt;p&gt;You wire up a new plugin, push a PR, walk away to make coffee, and 30 minutes later CI fails on a test you didn’t expect to be related. You scroll through logs, eventually trace it back, and realize the problem was a single misaligned argument in your &lt;code&gt;activate&lt;/code&gt; function. TypeScript was happy. The editor showed no red squiggles. Nothing in your local toolchain said a word.&lt;/p&gt;
&lt;p&gt;And the slow CI feedback loop is only half the story. Some of these mistakes don’t fail CI at all, a hardcoded English &lt;code&gt;label&lt;/code&gt;, an &lt;code&gt;enum&lt;/code&gt; in a settings schema, a malformed token id. They pass tests, ship to users, and only surface at runtime. They’re the kind of bugs you hear about from users months later, if at all.&lt;/p&gt;
&lt;p&gt;JupyterLab, Jupyter Notebook, and the wider extension ecosystem follow a lot of project-specific patterns around plugins, commands, tokens, settings schemas, translations, and visual tests. These patterns aren’t enforced by TypeScript, because they live one level above the type system. They’re contracts about &lt;em&gt;how&lt;/em&gt; the framework expects code to be written, not about whether the code type-checks.&lt;/p&gt;
&lt;p&gt;A few examples of what generic linters and TypeScript can’t catch:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;JupyterFrontEndPlugin&lt;/code&gt; that requires &lt;code&gt;[TokenA, TokenB]&lt;/code&gt; but whose &lt;code&gt;activate&lt;/code&gt; function takes those arguments in the wrong order. Both are tokens, both type-check but the plugin fails at activation.&lt;/li&gt;
&lt;li&gt;A command added with &lt;code&gt;commands.addCommand(...)&lt;/code&gt; whose &lt;code&gt;label&lt;/code&gt; is a hardcoded English string. The extension ships, gets translated by the community, and that one button silently stays in English.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are real, recurring issues. They cost contributor time, they frustrate first-time contributors, and they pile onto reviewer load. They’re exactly the kind of knowledge that lives in the heads of long-time maintainers and exactly the kind of knowledge that a linter is great at encoding. So we built one.&lt;/p&gt;
&lt;h2 id="what-v1-catches"&gt;What v1 catches&lt;/h2&gt;
&lt;p&gt;The plugin currently ships eight rules, grouped by what they protect:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plugin structure&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/plugin-activation-args/"&gt;&lt;code&gt;plugin-activation-args&lt;/code&gt;&lt;/a&gt; — Keeps &lt;code&gt;activate&lt;/code&gt; function arguments aligned with &lt;code&gt;requires&lt;/code&gt; and &lt;code&gt;optional&lt;/code&gt; tokens.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/plugin-description/"&gt;&lt;code&gt;plugin-description&lt;/code&gt;&lt;/a&gt; — Requires a non-empty &lt;code&gt;description&lt;/code&gt; on every &lt;code&gt;JupyterFrontEndPlugin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/token-format/"&gt;&lt;code&gt;token-format&lt;/code&gt;&lt;/a&gt; — Enforces the &lt;code&gt;&amp;lt;package&amp;gt;:&amp;lt;TokenSymbol&amp;gt;&lt;/code&gt; convention for &lt;code&gt;new Token(...)&lt;/code&gt; ids.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Commands and settings&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/command-described-by/"&gt;&lt;code&gt;command-described-by&lt;/code&gt;&lt;/a&gt; — Requires a &lt;code&gt;describedBy&lt;/code&gt; schema on commands that accept &lt;code&gt;args&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/no-schema-enum/"&gt;&lt;code&gt;no-schema-enum&lt;/code&gt;&lt;/a&gt; — Disallows &lt;code&gt;enum&lt;/code&gt; in settings JSON schemas in favor of translatable &lt;code&gt;oneOf&lt;/code&gt; with &lt;code&gt;const&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Internationalization&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/no-untranslated-string/"&gt;&lt;code&gt;no-untranslated-string&lt;/code&gt;&lt;/a&gt; — Flags hardcoded user-facing strings in command labels, dialogs, accessibility attributes, JSX, and more.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/no-translation-concatenation/"&gt;&lt;code&gt;no-translation-concatenation&lt;/code&gt;&lt;/a&gt; — Forbids dynamic concatenation inside &lt;code&gt;trans.__()&lt;/code&gt; calls that translation extractors can’t pick up.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/require-soft-assertions-before-snapshots/"&gt;&lt;code&gt;require-soft-assertions-before-snapshots&lt;/code&gt;&lt;/a&gt; — Requires &lt;code&gt;expect.soft(...)&lt;/code&gt; for all but the last snapshot in a Playwright test, so a single failure doesn’t block the rest from updating.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each rule has incorrect/correct examples, options, and rationale in the &lt;a href="https://eslint-plugin.readthedocs.io/en/latest/category/rules/"&gt;rules reference&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="getting-started-in-60-seconds"&gt;Getting started in 60 seconds&lt;/h2&gt;
&lt;p&gt;Install the plugin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--save-dev&lt;span class="w"&gt; &lt;/span&gt;@jupyter/eslint-plugin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Add it to your ESLint fconfig and turn on the recommended ruleset:&lt;/p&gt;
&lt;p&gt;javascript&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="nx"&gt;jupyterPlugin&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;@jupyter/eslint-plugin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&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="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="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;plugins&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;jupyter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jupyterPlugin&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="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;jupyterPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s it. Run &lt;code&gt;jlpm eslint&lt;/code&gt; and you’ll start seeing Jupyter-specific issues alongside your existing lint output. If you’re still on legacy &lt;code&gt;.eslintrc&lt;/code&gt; configs, there’s a &lt;a href="https://eslint-plugin.readthedocs.io/en/latest/user-guide/#for-legacy-eslintrc-configs"&gt;&lt;code&gt;recommended-legacy&lt;/code&gt;&lt;/a&gt; config too. However, we strongly recommend upgrading to &lt;a href="https://eslint.org/docs/latest/use/migrate-to-9.0.0"&gt;ESLint v9&lt;/a&gt; and using the new &lt;a href="https://eslint.org/blog/2022/08/new-config-system-part-2/"&gt;flat config&lt;/a&gt; format.&lt;/p&gt;
&lt;p&gt;Full setup details, including type-aware configuration for &lt;code&gt;plugin-activation-args&lt;/code&gt; and the JSON parser setup for &lt;code&gt;no-schema-enum&lt;/code&gt;, are in the &lt;a href="https://eslint-plugin.readthedocs.io/en/latest/user-guide/"&gt;user guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you maintain an extension, &lt;strong&gt;the most useful thing you can do today is install it and run it once on your codebase.&lt;/strong&gt; Even if you don’t adopt it permanently, the first run is often informative most extensions surface a handful of issues they didn’t know they had.&lt;/p&gt;
&lt;h2 id="help-us-encode-more-knowledge"&gt;Help us encode more knowledge&lt;/h2&gt;
&lt;p&gt;Eight rules is a starting point. The plugin is most valuable when it captures the recurring mistakes that maintainers across the ecosystem keep running into and a lot of that knowledge isn’t written down anywhere.&lt;/p&gt;
&lt;p&gt;If you’ve ever been bitten by a Jupyter-specific issue that &lt;em&gt;could&lt;/em&gt; have been a lint rule, something where you found yourself thinking “I wish my editor had warned me about this” please &lt;a href="https://github.com/jupyterlab/eslint-plugin/issues"&gt;open an issue on the repository&lt;/a&gt;. Even a short description of the bug and a minimal example helps. We’d much rather encode that knowledge into an automated check once than have every new contributor learn it the hard way.&lt;/p&gt;
&lt;h2 id="acknowledgement"&gt;Acknowledgement&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;We’re also grateful to &lt;a href="https://github.com/jtpio"&gt;Jeremy Tuloup&lt;/a&gt; and &lt;a href="https://github.com/brichet"&gt;Nicolas Brichet&lt;/a&gt; for their help in adopting the plugin.&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 development of v1 of &lt;code&gt;@jupyter/eslint-plugin&lt;/code&gt;, designing and implementing the rule set, the documentation, and the release.&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.&lt;/p&gt;
&lt;h2 id="useful-links"&gt;Useful links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://www.npmjs.com/package/@jupyter/eslint-plugin"&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://eslint-plugin.readthedocs.io/en/latest/"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🐙 &lt;a href="https://github.com/jupyterlab/eslint-plugin"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://github.com/jupyterlab/eslint-plugin/issues"&gt;Open an issue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="JavaScript"/></entry></feed>