Develop Extensions#

The JupyterLab application is comprised of a core application object and a set of extensions. JupyterLab extensions provide nearly every function in JupyterLab, including notebooks, document editors and viewers, code consoles, terminals, themes, the file browser, contextual help system, debugger, and settings editor. Extensions even provide more fundamental parts of the application, such as the menu system, status bar, and the underlying communication mechanism with the server.

A JupyterLab extension is a package that contains a number of JupyterLab plugins. We will discuss how to write a plugin, then how to package together a set of plugins into a JupyterLab extension.

See the sections below for more detailed information, or browse the rest of this page for an overview.

Other resources#

Before we get started, here are some resources for hands-on practice or more in-depth reference documentation.

Tutorials#

Learn how to write JupyterLab extensions with these guides:

Extension template#

We provide several templates to create JupyterLab extensions:

API Reference Documentation#

Here is some autogenerated API documentation for JupyterLab and Lumino packages:

Overview of Extensions#

A JupyterLab plugin is the basic unit of extensibility in JupyterLab. An extension is a package that contains one or more JupyterLab plugins. Extensions can be distributed in two ways:

  • Aprebuilt extension (since JupyterLab 3.0) distributes a bundle of JavaScript code prebuilt from a source extension that can be loaded into JupyterLab without rebuilding JupyterLab. In this case, the extension developer uses tools provided by JupyterLab to compile a source extension into a JavaScript bundle that includes the non-JupyterLab JavaScript dependencies, then distributes the resulting bundle in, for example, a Python pip or conda package. Installing a prebuilt extensions does not require Node.js.

  • [DEPRECATED] Asource extension is a JavaScript (npm) package that exports one or more plugins. Installing a source extension requires a user to rebuild JupyterLab. This rebuilding step requires Node.js and may take a lot of time and memory, so some users may not be able to install a source extension. However, the total size of the JupyterLab code delivered to a user’s browser may be reduced compared to using prebuilt extensions. SeeDeduplication of Dependencies for the technical reasons for rebuilding JupyterLab when a source extension is installed.

An extension can be published both as a source extension on NPM and as a prebuilt extension (e.g., published as a Python package). In some cases, system administrators may even choose to install a prebuilt extension by directly copying the prebuilt bundle to an appropriate directory, circumventing the need to create a Python package. If a source extension and a prebuilt extension with the same name are installed in JupyterLab, the prebuilt extension takes precedence.

Because prebuilt extensions do not require a JupyterLab rebuild, they have a distinct advantage in multi-user systems where JupyterLab is installed at the system level. On such systems, only the system administrator has permissions to rebuild JupyterLab and install source extensions. Since prebuilt extensions can be installed at the per-user level, the per-environment level, or the system level, each user can have their own separate set of prebuilt extensions that are loaded dynamically in their browser on top of the system-wide JupyterLab.

Warning

Your extensions may break with new releases of JupyterLab. As noted inBackwards Compatibility, Versions and Breaking Changes,JupyterLab development and release cycles follow semantic versioning, so we recommend planningyour development process to account for possible future breaking changes that may disrupt usersof your extensions. Consider documenting your maintenance plans to users in your project, orsetting an upper bound on the version of JupyterLab your extension is compatible with in yourproject’s package metadata.

Tip

We recommend publishing prebuilt extensions in Python packages for user convenience.

Plugins#

A JupyterLab plugin is the basic unit of extensibility in JupyterLab. JupyterLab supports several types of plugins:

  • Application plugins: Application plugins are the fundamental building block of JupyterLab functionality. Application plugins interact with JupyterLab and other plugins by requiring services provided by other plugins, and optionally providing their own service to the system. Application plugins in core JupyterLab include the main menu system, the file browser, and the notebook, console, and file editor components.

  • Mime renderer plugins: Mime renderer plugins are simplified, restricted ways to extend JupyterLab to render custom mime data in notebooks and files. These plugins are automatically converted to equivalent application plugins by JupyterLab when they are loaded. Examples of mime renderer plugins that come in core JupyterLab are the pdf viewer, the JSON viewer, and the Vega viewer.

  • Theme plugins: Theme plugins provide a way to customize the appearance of JupyterLab by changing themeable values (i.e., CSS variable values) and providing additional fonts and graphics to JupyterLab. JupyterLab comes with light and dark theme plugins.

Application Plugins#

An application plugin is a JavaScript object with a number of metadata fields. A typical application plugin might look like this in TypeScript:

constplugin:JupyterFrontEndPlugin<MyToken>={id:'my-extension:plugin',description:'Provides a new service.',autoStart:true,requires:[ILabShell,ITranslator],optional:[ICommandPalette],provides:MyToken,activate:activateFunction};

Theid andactivate fields are required and the other fields may be omitted. For more information about how to use therequires,optional, orprovides fields, seePlugins Interacting with Each Other.

  • id is a required unique string. The convention is to use the NPM extension package name, a colon, then a string identifying the plugin inside the extension.

  • description is an optional string. It allows to document the purpose of a plugin.

  • autostart indicates whether your plugin should be activated at application startup. Typically this should betrue. If it isfalse or omitted, your plugin will be activated when any other plugin requests the token your plugin is providing.

  • requires andoptional are lists oftokens corresponding to services other plugins provide. These services will be given as arguments to theactivate function when the plugin is activated. If arequires service is not registered with JupyterLab, an error will be thrown and the plugin will not be activated.

  • provides is thetoken associated with the service your plugin is providing to the system. If your plugin does not provide a service to the system, omit this field and do not return a value from youractivate function.

  • activate is the function called when your plugin is activated. The arguments are, in order, theapplication object, the services corresponding to therequires tokens, then the services corresponding to theoptional tokens (ornull if that particularoptional token is not registered in the system). If aprovides token is given, the return value of theactivate function (or resolved return value if a promise is returned) will be registered as the service associated with the token.

  • deactivate is the optional deactivation method

For more information, see the API reference forJupyterFrontEndPlugin.

Application Object#

A Jupyter front-end application object is given to a plugin’sactivate function as its first argument. The application object has a number of properties and methods for interacting with the application, including:

  • commands - an extensible registry used to add and execute commands in the application.

  • docRegistry - an extensible registry containing the document types that the application is able to read and render.

  • restored - a promise that is resolved when the application has finished loading.

  • serviceManager - low-level manager for talking to the Jupyter REST API.

  • shell - a generic Jupyter front-end shell instance, which holds the user interface for the application. SeeJupyter Front-End Shell for more details.

See the JupyterLab API reference documentation for theJupyterFrontEnd class for more details.

Plugins Interacting with Each Other#

(The Provider-Consumer pattern is explained here)

One of the foundational features of the JupyterLab plugin system is that application plugins can interact with other plugins by providing a service to the system and requiring services provided by other plugins. A service can be any JavaScript value, and typically is a JavaScript object with methods and data attributes. For example, the core plugin that supplies the JupyterLab main menu provides aMain Menu service object to the system with a method to add a new top-level menu and attributes to interact with existing top-level application menus.

In the following discussion, the plugin that is providing a service to the system is theprovider plugin, and the plugin that is requiring and using the service is theconsumer plugin. Note that these kinds ofprovider andconsumer plugins are fundamental parts of JupyterLab’s Provider-Consumer pattern (which is a type ofdependency-injection pattern).

Tokens#

A service provided by a plugin is identified by atoken, i.e., a concrete instance of the Lumino Token class. The provider plugin lists the token in its plugin metadataprovides field, and returns the associated service from itsactivate function.

Consumer plugins import the token (for example, from the provider plugin’s extension JavaScript package, or from a third package exporting the token for both the provider and consumer) and list the token in their plugin metadatarequires oroptional fields. When JupyterLab instantiates the consumer plugin by calling itsactivate function, it will pass in the service associated with the token as an argument. If the service is not available (i.e., the token has not been registered with JupyterLab), then JupyterLab will either throw an error and not activate the consumer (if the token was listed inrequires), or will set the correspondingactivate argument tonull (if the token was listed inoptional). JupyterLab orders plugin activation to ensure that a provider of a service is activated before its consumers. A token can only be registered with the system once.

A consumer might list a token asoptional when the service it identifies is not critical to the consumer, but would be nice to have if the service is available. For example, a consumer might list the status bar service as optional so that it can add an indicator to the status bar if it is available, but still make it possible for users running a customized JupyterLab distribution without a status bar to use the consumer plugin.

A token defined in TypeScript can also define a TypeScript interface for the service associated with the token. If a package using the token uses TypeScript, the service will be type-checked against this interface when the package is compiled to JavaScript.

Note

JupyterLab uses tokens to identify services (instead of strings, for example) to prevent conflicts between identifiers and to enable type checking when using TypeScript.

Publishing Tokens#

Since consumers will need to import a token used by a provider, the token should be exported in a published JavaScript package. Tokens will need to be deduplicated in JupyterLab—seeDeduplication of Dependencies for more details.

A pattern in core JupyterLab is to create and export a token from a third package that both the provider and consumer extensions import, rather than defining the token in the provider’s package. This enables a user to swap out the provider extension for a different extension that provides the same token with an alternative service implementation. For example, the core JupyterLabfilebrowser package exports a token representing the file browser service (enabling interactions with the file browser). Thefilebrowser-extension package contains a plugin that implements the file browser in JupyterLab and provides the file browser service to JupyterLab (identified with the token imported from thefilebrowser package). Extensions in JupyterLab that want to interact with the filebrowser thus do not need to have a JavaScript dependency on thefilebrowser-extension package, but only need to import the token from thefilebrowser package. This pattern enables users to seamlessly change the file browser in JupyterLab by writing their own extension that imports the same token from thefilebrowser package and provides it to the system with their own alternative file browser service.

MIME Renderer Plugins#

MIME Renderer plugins are a convenience for creating a pluginthat can render mime data in a notebook and files of the given mime type. MIME renderer plugins are more declarative and more restricted than standard plugins.A mime renderer plugin is an object with the fields listed in theIExtensionobject.

JupyterLab has apdf mime renderer extension, for example. In core JupyterLab, this is used to view pdf files and view pdf data mime data in a notebook.

We have aMIME renderer example walking through creating a mime renderer extension which adds mp4 video rendering to JupyterLab. Theextension template supports MIME renderer extensions.

The mime renderer can update its data by calling.setData() on themodel it is given to render. This can be used for example to add apng representation of a dynamic figure, which will be picked up by anotebook model and added to the notebook document. When usingIDocumentWidgetFactoryOptions,you can update the document model bycalling.setData() with updated data for the rendered MIME type. Thedocument can then be saved by the user in the usual manner.

Theme plugins#

A theme is a special application plugin that registers a theme with theThemeManager service. Theme CSS assets are specially bundled in an extension (seeTheme path) so they can be unloaded or loaded as the theme is activated. Since CSS files referenced by thestyle orstyleModule keys are automatically bundled and loaded on the page, the theme files should not be referenced by these keys.

The extension package containing the theme plugin must include all static assets that are referenced by@import in its theme CSS files. Local URLs can be used to reference files relative to the location of the referring sibling CSS files. For exampleurl('images/foo.png') orurl('../foo/bar.css') can be used to refer local files in the theme. Absolute URLs (starting with a/) or external URLs (e.g.https:) can be used to refer to external assets.

See theJupyterLab Light Theme for an example.

See theTypeScript extension template (choosingtheme askind ) for a quick start to developing a theme plugin.

Service Manager Plugins#

Warning

This is an advanced topic. If you are new to JupyterLab extensions, you can skip this section.

The Service Manager is a core component of a JupyterLab application and the interface to the Jupyter Server REST API.Before JupyterLab 4.4.0, the Service Manager had to be created as a singleton object and passed when creating a JupyterLab application object.This was not convenient if some extensions needed to change the behavior of some of the core services provided by the Service Manager,as they would have to build a new JupyterLab application from scratch.

Added in version 4.4:The Service Manager is now itself a plugin which can be provided by a third-party extension using the :ts:variable:services.IServiceManager token. Its underlying services (such as the kernel manager and the contents manager) are also now available as plugins.

The Service Manager plugins can be provided by third-party extensions via the following tokens:

The following example shows how you can provide a custom contents manager service, which logs the path of the requested content in the console:

import{Contents,ContentsManager,IContentsManager,ServiceManagerPlugin}from'@jupyterlab/services';classCustomContentsextendsContentsManager{asyncget(path:string,options?:Contents.IFetchOptions):Promise<Contents.IModel>{console.log('CustomContents.get',path);returnsuper.get(path,options);}}constplugin:ServiceManagerPlugin<IContentsManager>={id:'my-extension:contents-manager',autoStart:true,provides:IContentsManager,description:'A JupyterLab extension providing a custom contents manager',activate:(_:null):Contents.IManager=>{returnnewCustomContents();}};exportdefaultplugin;

Warning

Note the use ofServiceManagerPlugin to declare the plugin.ServiceManagerPlugin is different fromJupyterFrontEndPlugin in that it provides a service manager plugin, which will beactivated before the application is set. As a consequence, the first parameter of theactivate function isnull.ServiceManagerPlugin<T> is equivalent toIPlugin<null,T> whereT is the service provided by the plugin.

Source Extensions#

A source extension is a JavaScript (npm) package that exports one or more plugins. All JupyterLab extensions are developed as source extensions (for example, prebuilt extensions are built from source extensions).

A source extension has metadata in thejupyterlab field of itspackage.json file. TheJSON schema for the metadata is distributed in the@jupyterlab/builder package.

We will talk about eachjupyterlab metadata field inpackage.json for source extensions below.

A JupyterLab extension must have at least one ofjupyterlab.extension orjupyterlab.mimeExtension set.

Application Plugins#

Thejupyterlab.extension field signifies that the package exports one or more JupyterLab application plugins. Set the value totrue if the default export of the main package module (i.e., the file listed in themain key ofpackage.json) is an application plugin or a list of application plugins. If your plugins are exported as default exports from a different module, set this to the relative path to the module (e.g.,"lib/foo"). Example:

"jupyterlab":{"extension":true}

MIME Renderer Plugins#

Thejupyterlab.mimeExtension field signifies that the package exports mime renderer plugins. Like thejupyterlab.extension field, the value can be a boolean (indicating a mime renderer plugin or list of mime renderer plugins is the default export from themain field), or a string, which is the relative path to the module exporting (as the default export) one or more mime renderer plugins.

Theme path#

Theme plugin assets (e.g., CSS files) need to bundled separately from a typical application plugin’s assets so they can be loaded and unloaded as the theme is activated or deactivated. If an extension exports a theme plugin, it should give the relative path to the theme assets in thejupyterlab.themePath field:

"jupyterlab":{"extension":true,"themePath":"style/theme.css"}

An extension cannot bundle multiple theme plugins.

Ensure that the theme path files are included in thefiles metadata inpackage.json. If you want to use SCSS, SASS, or LESS files, you must compile them to CSS and pointjupyterlab.themePath to the CSS files.

Plugin Settings#

JupyterLab exposes a plugin settings system that can be used to providedefault setting values and user overrides. A plugin’s settings are specified with a JSON schema file. Thejupyterlab.schemaDir field inpackage.json gives the relative location of the directory containing plugin settings schema files.

The setting system relies on plugin ids following the convention<source_package_name>:<plugin_name>. The settings schema file for the pluginplugin_name is<schemaDir>/<plugin_name>.json.

For example, the JupyterLabfilebrowser-extension package exports the@jupyterlab/filebrowser-extension:browser plugin. In thepackage.json for@jupyterlab/filebrowser-extension, we have:

"jupyterlab":{"schemaDir":"schema",}

The file browser setting schema file (which specifies some default keyboard shortcuts and other settings for the filebrowser) is located inschema/browser.json (seehere).

See thefileeditor-extensionfor another example of an extension that uses settings.

Please ensure that the schema files are included in thefiles metadata inpackage.json.

When declaring dependencies on JupyterLab packages, use the^ operator before a package version so that the build system installs the newest patch or minor version for a given major version. For example,^4.0.0 will install version 4.0.0, 4.0.1, 4.1.0, etc.

A system administrator or user can override default values provided in a plugin’s settings schema file with theoverrides.json file.

Disabling other extensions#

Thejupyterlab.disabledExtensions field gives a list of extensions or plugins to disable when this extension is installed, with the same semantics as thedisabledExtensions field ofpage_config.json. This is useful if your extension overrides built-in extensions. For example, if an extension replaces the@jupyterlab/filebrowser-extension:share-file plugin tooverride the “Copy Shareable Link” functionality in the file browser, it can automatically disable the@jupyterlab/filebrowser-extension:share-file plugin with:

"jupyterlab":{"disabledExtensions":["@jupyterlab/filebrowser-extension:share-file"]}

To disable all plugins in an extension, give the extension package name, e.g.,"@jupyterlab/filebrowser-extension" in the above example.

Deduplication of Dependencies#

Thejupyterlab.sharedPackages field controls how dependencies are bundled, shared, and deduplicated with prebuilt extensions.

One important concern and challenge in the JupyterLab extension system is deduplicating dependencies of extensions instead of having extensions use their own bundled copies of dependencies. For example, the Lumino widgets system on which JupyterLab relies for communication across the application requires all packages use the same copy of the@lumino/widgets package.Tokens identifying plugin services also need to be shared across the providers and consumers of the services, so dependencies that export tokens need to be deduplicated.

JupyterLab automatically deduplicates the entire dependency tree between source extensions when it rebuilds itself during a source extension installation. Deduplication between source and prebuilt extensions, or between prebuilt extensions themselves, is a more nuanced problem (for those curious about implementation details, this deduplication in JupyterLab is powered by the Webpack 5.0module federation system). JupyterLab comes with a reasonable default strategy for deduplicating dependencies for prebuilt extensions. Thejupyterlab.sharedPackages object in an extension’spackage.json enables an extension author to modify the default deduplication strategy for a given dependency with three boolean options. The keys of this object are dependency package names, and the values are eitherfalse (signifying that dependency should not be shared/deduplicated), or objects with up to three fields:

  • bundled: iftrue (default), the dependency is bundled with the extension and is made available as one of the copies available to JupyterLab. Iffalse, the dependency is not bundled with the extension, so the extension will use a version of the dependency from a different extension.

  • singleton: iftrue, the extension will always prefer to use the copy of the dependency that other extensions are using, rather than using the highest version available. The default isfalse.

  • strictVersion: iftrue, the extension will always make sure the copy of the dependency it is using satisfies the dependency version range it requires.

By default, JupyterLab deduplicates direct dependencies of prebuilt extensions with direct dependencies of other source and prebuilt extensions, choosing the highest version of a dependency available to JupyterLab. JupyterLab chooses reasonable default options when using tokens and services from core JupyterLab packages. We suggest the followingsharedPackages configurations when using tokens provided by packages other than core JupyterLab packages (seePlugins Interacting with Each Other for more details about using tokens).

Providing a service#

When an extension (the “provider”) is providing a service identified by a token that is imported from a dependencytoken-package, the provider should configure the dependency as a singleton. This makes sure the provider is identifying the service with the same token that others are importing. Iftoken-package is not a core package, it will be bundled with the provider and available for consumers to import if theyrequire the service.

"jupyterlab":{"sharedPackages":{"token-package":{"singleton":true}}}

Requiring a service#

When an extension (the “consumer”) is requiring a service provided by another extension (the “provider”), identified by a token imported from a package (thetoken-package, which may be the same as the provider), the consumer should configure the dependencytoken-package to be a singleton to ensure the consumer is getting the exact same token the provider is using to identify the service. Also, since the provider is providing a copy oftoken-package, the consumer can exclude it from its bundle.

"jupyterlab":{"sharedPackages":{"token-package":{"bundled":false,"singleton":true}}}

Optionally using a service#

When an extension (the “consumer”) is optionally using a service identified by a token imported from a package (thetoken-package), there is no guarantee that a provider is going to be available and bundlingtoken-package. In this case, the consumer should only configuretoken-package to be a singleton:

"jupyterlab":{"sharedPackages":{"token-package":{"singleton":true}}}

Companion packages#

If your extension depends on the presence of one or more packages in thekernel, or on a notebook server extension, you can add metadata to indicatethis to the extension manager by adding metadata to your package.json file.The full options available are:

"jupyterlab":{"discovery":{"kernel":[{"kernel_spec":{"language":"<regexp for matching kernel language>","display_name":"<regexp for matching kernel display name>"//optional},"base":{"name":"<the name of the kernel package>"},"overrides":{//optional"<manager name, e.g. 'pip'>":{"name":"<name of kernel package on pip, if it differs from base name>"}},"managers":[//listofpackagemanagersthathaveyourkernelpackage"pip","conda"]}],"server":{"base":{"name":"<the name of the server extension package>"},"overrides":{//optional"<manager name, e.g. 'pip'>":{"name":"<name of server extension package on pip, if it differs from base name>"}},"managers":[//listofpackagemanagersthathaveyourserverextensionpackage"pip","conda"]}}}

A typical setup for e.g. a jupyter-widget based package will then be:

"keywords":["jupyterlab-extension","jupyter","widgets","jupyterlab"],"jupyterlab":{"extension":true,"discovery":{"kernel":[{"kernel_spec":{"language":"^python",},"base":{"name":"myipywidgetspackage"},"managers":["pip","conda"]}]}}

Currently supported package managers arepip andconda.

Extension CSS#

If your extension has a top-levelstyle key inpackage.json, the CSS file it points to will be included on the page automatically.

A convention in JupyterLab for deduplicating CSS on the page is that if your extension has a top-levelstyleModule key inpackage.json giving a JavaScript module that can be imported, it will be imported (as a JavaScript module) instead of importing thestyle key CSS file as a CSS file.

Prebuilt Extensions#

package.json metadata#

In addition to the package metadata forsource extensions, prebuilt extensions have extrajupyterlab metadata.

Output Directory#

When JupyterLab builds the prebuilt extension, it creates a directory of files which can then be copied into theappropriate install location. Thejupyterlab.outputDir field gives the relative path to the directory where these JavaScript and other files should be placed. A copy of thepackage.json file with additional build metadata will be put in theoutputDir and the JavaScript and other files that will be served are put into thestatic subdirectory.

"jupyterlab":{"outputDir":"mypackage/labextension"}

Custom webpack config#

Warning

This feature isexperimental and may change without notice since it exposes internal implementation details (namely webpack). Be careful in using it, as a misconfiguration may break the prebuilt extension system.

The prebuilt extension system uses the WebpackModule Federation System. Normally this is an implementation detail that prebuilt extension authors do not need to worry about, but occasionally extension authors will want to tweak the configuration used to build their extension to enable various webpack features. Extension authors can specify a custom webpack config file that will be merged with the webpack config generated by the prebuilt extension system using thejupyterlab.webpackConfig field inpackage.json. The value should be the relative path to the config file:

"jupyterlab":{"webpackConfig":"./webpack.config.js"}

Custom webpack configuration can be used to enable webpack features, configure additional file loaders, and for many other things. Here is an example of awebpack.config.js custom config that enables the async WebAssembly and top-levelawait experimental features of webpack:

module.exports={experiments:{topLevelAwait:true,asyncWebAssembly:true}};

This custom config will be merged with theprebuilt extension configwhen building the prebuilt extension.

Developing a prebuilt extension#

Build a prebuilt extension using thejupyterlabextensionbuild command. This command uses dependency metadata from the active JupyterLab to produce a set of files from a source extension that comprise the prebuilt extension. The files include a main entry pointremoteEntry.<hash>.js, dependencies bundled into JavaScript files,package.json (with some extra build metadata), as well as plugin settings and theme directory structures if needed.

While authoring a prebuilt extension, you can use thelabextensiondevelop command to create a link to your prebuilt output directory, similar topipinstall-e:

jupyterlabextensiondevelop.--overwrite

Then rebuilding your extension and refreshing JupyterLab in the browser should pick up changes in your prebuilt extension source code.

If using Windows, you may need to configure your operating system for thedevelop command described above to work, please see the note:Important for Windows users

If you are developing your prebuilt extension against the JupyterLab source repo, you can run JupyterLab withjupyterlab--dev-mode--extensions-in-dev-mode to have the development version of JupyterLab load prebuilt extensions. It would be best if you had in mind that the JupyterLab packages that your extension depends on may differ from those published; this means that your extension doesn’t build with JupyterLab dependencies from your node_modules folder but those in JupyterLab source code.

If you are using TypeScript, the TypeScript compiler would complain because the dependencies of your extension may differ from those in JupyterLab. For that reason, you need to add to yourtsconfig.json the path where to search for these dependencies by adding the optionpaths:

{"compilerOptions":{"baseUrl":".","paths":{"@jupyterlab/*":["../jupyterlab/packages/*"],"*":["node_modules/*"]}}}

When adding the path to find JupyterLab dependencies, it may cause troubles with other dependencies (like lumino or react) in your project because JupyterLab packages will take its dependencies from JupyterLabnode_modules folder. In contrast, your packages will take them from yournode_modules folder. To solve this problem, you’ll need to add the dependencies with conflicts toresolutions in yourpackage.json. This way, both projects (JupyterLab and your extension) use the same version of the duplicated dependencies.

We provide aextension template that handles all of the scaffolding for an extension author, including the shipping of appropriate data files, so that when the user installs the package, the prebuilt extension ends up inshare/jupyter/labextensions

Distributing a prebuilt extension#

Prebuilt extensions can be distributed by any system that can copy the prebuilt assets into an appropriate location where JupyterLab can find them. Theofficial extension template shows how to distribute prebuilt extensions via Python pip or conda packages. A system package manager, or even just an administrative script that copies directories, could be used as well.

To distribute a prebuilt extension, copy itsoutput directory to a location where JupyterLab will find it, typically<sys-prefix>/share/jupyter/labextensions/<package-name>, where<package-name> is the JavaScript package name in thepackage.json. For example, if your JavaScript package name is@my-org/my-package, then the appropriate directory would be<sys-prefix>/share/jupyter/labextensions/@my-org/my-package.

The JupyterLab server makes thestatic/ files available via a/labextensions/ server handler. The settings and themes handlers in the server also load settings and themes from the prebuilt extension directories. If a prebuilt extension has the same name as a source extension, the prebuilt extension is preferred.

Packaging Information#

Since prebuilt extensions are distributed in many ways (Python pip packages, conda packages, and potentially in many other packaging systems), the prebuilt extension directory can include an extra file,install.json, that helps the user know how a prebuilt extension was installed and how to uninstall it. This file should be copied by the packaging system distributing the prebuilt extension into the top-level directory, for example<sys-prefix>/share/jupyter/labextensions/<package-name>/install.json.

Thisinstall.json file is used by JupyterLab to help a user know how to manage the extension. For example,jupyterlabextensionlist includes information from this file, andjupyterlabextensionuninstall can print helpful uninstall instructions. Here is an exampleinstall.json file:

{"packageManager":"python","packageName":"mypackage","uninstallInstructions":"Use your Python package manager (pip, conda, etc.) to uninstall the package mypackage"}
  • packageManager: This is the package manager that was used to install the prebuilt extension, for example,python,pip,conda,debian,systemadministrator, etc.

  • packageName: This is the package name of the prebuilt extension in the package manager above, which may be different than the package name inpackage.json.

  • uninstallInstructions: This is a short block of text giving the user instructions for uninstalling the prebuilt extension. For example, it might instruct them to use a system package manager or talk to a system administrator.

PyPI Trove Classifiers#

Extensions distributed as Python packages may declare additional metadata in the form oftrove classifiers. These improve the browsingexperience for users onPyPI. While including the license,development status, Python versions supported, and other topic classifiers are usefulfor many audiences, the following classifiers are specific to Jupyter and JupyterLab.

Framework::JupyterFramework::Jupyter::JupyterLabFramework::Jupyter::JupyterLab::1Framework::Jupyter::JupyterLab::2Framework::Jupyter::JupyterLab::3Framework::Jupyter::JupyterLab::4Framework::Jupyter::JupyterLab::ExtensionsFramework::Jupyter::JupyterLab::Extensions::MimeRenderersFramework::Jupyter::JupyterLab::Extensions::PrebuiltFramework::Jupyter::JupyterLab::Extensions::Themes

Include each relevant classifier (and its parents) to help describe what your packageprovides to prospective users in yoursetup.py,setup.cfg, orpyproject.toml.In particularFramework::Jupyter::JupyterLab::Extensions::Prebuilt is used bythe extension manager to get the available extensions from PyPI.org.

Hint

For example, a theme, only compatible with JupyterLab 3, and distributed asa ready-to-run, prebuilt extension might look like:

# setup.pysetup(# the rest of the package's metadata# ...classifiers=["Framework :: Jupyter","Framework :: Jupyter :: JupyterLab","Framework :: Jupyter :: JupyterLab :: 3","Framework :: Jupyter :: JupyterLab :: Extensions","Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt","Framework :: Jupyter :: JupyterLab :: Extensions :: Themes",])

This would be discoverable from, for example, aPyPI search for theme extensions.

Development workflow for source extensions#

Developing prebuilt extensions is usually much easier since they do not require rebuilding JupyterLab to see changes. If you need to develop a source extension, here are some tips for a development workflow.

While authoring a source extension, you can use the command:

jlpminstall# install npm package dependenciesjlpmrunbuild# optional build step if using TypeScript, babel, etc.jupyterlabextensioninstall# install the current directory as an extension

This causes the builder to re-install the source folder before buildingthe application files. You can re-build at any time usingjupyterlabbuild and it will reinstall these packages.

You can also link other localnpm packages that you are working onsimultaneously usingjupyterlabextensionlink; they will be re-installedbut not considered as extensions. Local extensions and linked packages areincluded injupyterlabextensionlist.

When using local extensions and linked packages, you can run the command

jupyterlab--watch

This will cause the application to incrementally rebuild when one of thelinked packages changes. Note that only compiled JavaScript files (andthe CSS files) are watched by the WebPack process. This means that ifyour extension is in TypeScript you’ll have to run ajlpmrunbuildbefore the changes will be reflected in JupyterLab. To avoid this stepyou can also watch the TypeScript sources in your extension which isusually assigned to thetsc-w shortcut. If webpack doesn’t seem todetect the changes, this can be related tothe number of available watches.

Note that the application is built againstreleased versions of thecore JupyterLab extensions. You should specify the version using the^operator, such as^4.0.0, so that the build system can use newer minor and patchversions of a package with a particular major version.If your extension depends on JupyterLabpackages, it should be compatible with the dependencies in thejupyterlab/static/package.json file. Note that building will always use the latest JavaScript packages that meet the dependency requirements of JupyterLab itself and any installed extensions. If you wish to test against aspecific patch release of one of the core JupyterLab packages you cantemporarily pin that requirement to a specific version in your owndependencies.

If you want to test a source extension against the unreleased versions of JupyterLab, you can run the command

jupyterlab--watch--splice-source

This command will splice the localpackages directory into the application directory, allowing you to build source extension(s)against the current development sources. To statically build spliced sources, usejupyterlabbuild--splice-source. Once a spliced build is created, any subsequent calls tojupyterlabextensionbuild will be in splice mode by default. A spliced build can be forced by callingjupyterlabextensionbuild--splice-source. Note thatdeveloping a prebuilt extension against a development version of JupyterLab is generally much easier than source package building.

The package should export EMCAScript 6 compatible JavaScript. It canimport CSS using the syntaxrequire('foo.css'). The CSS files canalso import CSS from other packages using the syntax@importurl('~foo/index.css'), wherefoo is the name of thepackage.

The following file types are also supported (both in JavaScript andCSS):json,html,jpg,png,gif,svg,js.map,woff2,ttf,eot.

If your package uses any other file type it must be converted to one ofthe above types orinclude a loader in the import statement.If you include a loader, the loader must be importable at build time, so ifit is not already installed by JupyterLab, you must add it as a dependencyof your extension.

If your JavaScript is written in any other dialect thanEMCAScript 6 (2015) it should be converted using an appropriate tool.You can use Webpack to pre-build your extension to use any of it’s featuresnot enabled in our build configuration. To build a compatible package setoutput.libraryTarget to"commonjs2" in your Webpack configuration.(seethis example repo).

If you publish your extension onnpm.org, users will be able to installit as simplyjupyterlabextensioninstall<foo>, where<foo> isthe name of the publishednpm package. You can alternatively provide ascript that runsjupyterlabextensioninstall against a local folderpath on the user’s machine or a provided tarball. Any validnpminstall specifier can be used injupyterlabextensioninstall (e.g.foo@latest,bar@3.0.0.0,path/to/folder, andpath/to/tar.gz).

We encourage extension authors to add thejupyterlab-extension GitHub topic to any GitHub extension repository.

Testing your extension#

Note

We highly recommend using theextension template to set up tests configuration.

There are a number of helper functions intestutils in this repo (whichis a publicnpm package called@jupyterlab/testutils) that can be used whenwriting tests for an extension. Seetests/test-application for an exampleof the infrastructure needed to run tests.

If you are usingjest to test your extension, you willneed to transpile the jupyterlab packages tocommonjs as they are using ES6 modulesthatnode does not support.

To transpile jupyterlab packages, you need to install the following package:

jlpmadd--devjest@types/jestts-jest@babel/core@^7@babel/preset-env@^7

Then injest.config.js, you will specify to use babel for js files and ignoreall node modules except the ES6 modules:

constjestJupyterLab=require('@jupyterlab/testutils/lib/jest-config');constesModules=['@jupyterlab/'].join('|');constbaseConfig=jestJupyterLab(__dirname);module.exports={...baseConfig,automock:false,collectCoverageFrom:['src/**/*.{ts,tsx}','!src/**/*.d.ts','!src/**/.ipynb_checkpoints/*'],coverageReporters:['lcov','text'],testRegex:'src/.*/.*.spec.ts[x]?$',transformIgnorePatterns:[...baseConfig.transformIgnorePatterns,`/node_modules/(?!${esModules}).+`]};

Finally, you will need to configure babel with ababel.config.js file containing:

module.exports=require('@jupyterlab/testutils/lib/babel.config');