Targeting Multiple Applications#

Make your extensions work in JupyterLab, Notebook 7+ and more!

Jupyter Notebook 7 is built with components from JupyterLab, and sinceboth use the same building blocks, that means your extension can workon both (or any other frontend built with JupyterLab components) withlittle or no modification depending on its design.

This guide will give you an overview of compatibility features, then atutorial and reference code covering some of the topics mentioned here.If you don’t know how to make extensions, you can read more about thebasics atthe Extension Tutorial or theExtensions page.

Getting started#

At a high level, extensions for JupyterLab and Jupyter Notebook bothtypically start from a template project, which you can download and set upusing instructions fromthe extension tutorial.Once your template is ready, you can start adding components and features to build your extension.

How Compatibility Works#

An extension for JupyterLab (and for Notebook 7) is made up of aseriesof bundledplugins,and those plugins typically use components from the interface toolkitLuminoas well as theJupyterLab API(among others) to help build your extension’s look and behavior. BothLumino and JupyterLab API are written in Typescript.

This is how basic compatibility features work: both apps use the same buildingblocks and methods. For instance, both JupyterLab and Notebook 7 accept Lumino widgetsas interface components, both apps enable you to add them to the interface byspecifying an “area” to place them into, and extensions for both use the samebasicJupyterFrontEndPlugin type.

How to Achieve Compatibility#

Compatibility can be achieved without additional effort in some simplecases. But for more complex cases, where the extension uses features inone app that are not available in other apps, you will need to decidehow to handle those features in the other apps.

The technical solutions to compatibility basically offer ways to disablefeatures for some apps, or to check for the presence of a particular appor feature and then adjust behaviors accordingly.

There are also design patterns and approaches that will make it easier toachieve compatibility in your extensions.

You can read more about those below. Here are some general tips for makingyour extensions compatible:

  • Use common features where possible, these offer compatibility withoutany extra effort

  • Avoid using app-specific features (like the JupyterLab status bar) withoutfirst checking if they are available either when your extension loads, orwhile it’s running (more on that further down)

  • Try to separate app-specific features from common features that aresupported by both apps

  • Decide what approach to take with any extension features that rely onapp-specific capabilities (like the JupyterLab status bar). You can disableor modify those features in other apps using the techniques listed furtherdown in this document.

Using Only Common Features#

If your extension only uses features that both JupyterLab and Notebook 7have, you can simply install it and it will seamlessly work in both JupyterLaband Notebook 7.

An extension that adds a single self-contained text widget to the top barof the UI, for instance, doesn’t need to do anything at all to be compatiblewith both JupyterLab and Notebook 7 (both apps have a top area that can hold thewidget, so it will be visible in both JupyterLab and Notebook 7 upon install andafter launch).

Seethis example video of acompatible top-bar-text-widget that works in both JupyterLab and Notebook 7out of the box, andread the full extension example code here.

Note that using features that are not common to both JupyterLab and Notebook 7 (orother apps) will break compatibility in apps where that feature is not availableunless special steps are taken (more on these further down). The status bar inJupyterLab is an example of a feature that is available in JupyterLab but not inNotebook 7.

Using Plugin Metadata to Drive Compatibility#

JupyterLab’s extension system is designed so that plugins can depend on andreuse features from one another. A key part of this approach isJupyterLab’s Provider-Consumer pattern (a type ofdependency-injectionpattern), and it is what enables the compatibility solutions discussed here.

Each plugin uses some properties (therequires andoptional properties) torequest features it wants which are provided by other plugins that have beenloaded into JupyterLab. When your plugin requests features, the system sendsthem to your plugin’s activate function if they are available.

You can build compatible extensions by taking advantage of these pluginproperties, and how the plugin system uses them:

  • When you designate a feature in therequires list of yourplugin, JupyterLab will only load your plugin if that feature is available.

  • By designating a feature in theoptional list, JupyterLab will pass youan object for it (if it’s available) ornull if it’s not.

So, these capabilities form the backbone of extension compatibility. You canuse them to make checks in your extensions that will allow them to function inboth JupyterLab and Jupyter Notebook 7 (and others).

JupyterLab itself is aprovider of many features through its built-in plugins,which you can read more about in theCommon Extension Points document.It’s a good idea to use these extension points while you’re building your extensions (andby doing so you are acting as theconsumer in JupyterLab’sProvider-Consumer pattern.

Testing for Optional Features#

Making an app-specific feature optional and checking if it is available beforeusing it, is one technique you can use to make your extensions compatible.

Take a look at a snippet fromthis example extensionin the examples repo (you can read the full extension example code there):

constplugin:JupyterFrontEndPlugin<void>={id:'@jupyterlab-examples/shout-button:plugin',description:'An extension that adds a button and message to the right toolbar, with optional status bar widget in JupyterLab.',autoStart:true,// The IStatusBar is marked optional here. If it's available, it will// be provided to the plugin as an argument to the activate function// (shown below), and if not it will be null.optional:[IStatusBar],// Make sure to list any 'requires' and 'optional' features as arguments// to your activate function (activate is always passed an Application,// then required arguments, then optional arguments)activate:(app:JupyterFrontEnd,statusBar:IStatusBar|null)=>{// ... Extension code ...}};

This plugin marksIStatusBar as optional, and adds an argument for it to theplugin’sactivate function (which will be called by JupyterLab when the extensionloads). IfIStatusBar is not available, the second argument to theactivatefunction will benull, as is the case when the extension is loaded in JupyterNotebook 7.

The extension always creates a common main widget, but when it comes time to use thestatus bar, the extension first checks if theIStatusBar is available, and only thenproceeds to create a status bar item. This allows the extension to run successfully in bothJupyterLab and Jupyter Notebook 7:

// Create a ShoutWidget and add it to the interface in the right sidebarconstshoutWidget:ShoutWidget=newShoutWidget();shoutWidget.id='JupyterShoutWidget';// Widgets need an idapp.shell.add(shoutWidget,'right');// Check if the status bar is available, and if so, make// a status bar widget to hold some informationif(statusBar){conststatusBarWidget=newShoutStatusBarSummary();statusBar.registerStatusItem('shoutStatusBarSummary',{item:statusBarWidget});// Connect to the messageShouted to be notified when a new message// is published and react to it by updating the status bar widget.shoutWidget.messageShouted.connect((widget:ShoutWidget,time:Date)=>{statusBarWidget.setSummary('Last Shout: '+widget.lastShoutTime?.toString()??'(None)');});}

Using Required Features to Switch Behaviors#

Another pattern you can follow is to export a list of plugins from yourextension, then use different “requires” features to select differentbehaviors based on which app the extension is currently running in.

Here is a snippet fromthis sample extensionwhich adds aclap button to the top area in JupyterLab, or to theright sidebar in Jupyter Notebook 7 (you can read the full extensionexample code there):

/** * Data for the @jupyterlab-examples/clap-button JupyterLab plugin. */constpluginJupyterLab:JupyterFrontEndPlugin<void>={id:'@jupyterlab-examples/clap-button:pluginLab',description:'Adds a clap button to the top area JupyterLab',autoStart:true,requires:[ILabShell],activate:(app:JupyterFrontEnd,labShell:ILabShell)=>{console.log('JupyterLab extension @jupyterlab-examples/clap-button is activated!');// Create a ClapWidget and add it to the interface in the top areaconstclapWidget=newClapWidget();clapWidget.id='JupyterLabClapWidgetLab';app.shell.add(clapWidget,'top');}};/** * Data for the @jupyterlab-examples/clap-button Jupyter Notebook plugin. */constpluginJupyterNotebook:JupyterFrontEndPlugin<void>={id:'@jupyterlab-examples/clap-button:pluginNotebook',description:'Adds a clap button to the right sidebar of Jupyter Notebook 7',autoStart:true,requires:[INotebookShell],activate:(app:JupyterFrontEnd,notebookShell:INotebookShell)=>{console.log('Jupyter Notebook extension @jupyterlab-examples/clap-button is activated!');// Create a ClapWidget and add it to the interface in the right areaconstclapWidget=newClapWidget();clapWidget.id='JupyterNotebookClapWidgetNotebook';app.shell.add(clapWidget,'right');}};/** * Gather all plugins defined by this extension */constplugins:JupyterFrontEndPlugin<void>[]=[pluginJupyterLab,pluginJupyterNotebook];exportdefaultplugins;

As you can see above, this extension exports multiple plugins in a list,and each plugin uses differentrequires features to switch betweendifferent behaviors (in this case, different layout areas) depending onthe app it is being loaded into. The first plugin requiresILabShell(available in JupyterLab), and the second plugin requiresINotebookShell(available in Jupyter Notebook 7).

This approach (testing the shell at plugin load time) is not the preferredmethod for making compatible extensions since it is less granular, lessuniversal (as the shell is specific to a given app generally) and offersonly very broad behavior switching, though it can be used to make specializedfeatures that target one particular app in your extensions. In general, youshould prefer the “Testing for Optional Features” approach and target the“Common Extension Points” mentioned above.

Further Reading#

For an explanation of JupyterLab’s plugin system and JupyterLab’s Provider-Consumer pattern (a type ofdependency-injectionpattern), read theExtension Development document.