Custom front-end extensions

This describes the basic steps to write a JavaScript extension for the Jupyter notebook front-end. This allows you to customize the behaviour of the various pages like the dashboard, the notebook, or the text editor.

The structure of a front-end extension

Note

The notebook front-end and Javascript API are not stable, and are subject to a lot of changes. Any extension written for the current notebook is almost guaranteed to break in the next release.

A front-end extension is a JavaScript file that defines an AMD module which exposes at least a function called load_ipython_extension, which takes no arguments. We will not get into the details of what each of these terms consists of yet, but here is the minimal code needed for a working extension:

// file my_extension/main.js

define(function(){

    function load_ipython_extension(){
        console.info('this is my first extension');
    }

    return {
        load_ipython_extension: load_ipython_extension
    };
});

Note

Although for historical reasons the function is called load_ipython_extension, it does apply to the Jupyter notebook in general, and will work regardless of the kernel in use.

If you are familiar with JavaScript, you can use this template to require any Jupyter module and modify its configuration, or do anything else in client-side Javascript. Your extension will be loaded at the right time during the notebook page initialisation for you to set up a listener for the various events that the page can trigger.

You might want access to the current instances of the various Jupyter notebook components on the page, as opposed to the classes defined in the modules. The current instances are exposed by a module named base/js/namespace. If you plan on accessing instances on the page, you should require this module rather than accessing the global variable Jupyter, which will be removed in future. The following example demonstrates how to access the current notebook instance:

// file my_extension/main.js

define([
    'base/js/namespace'
], function(
    Jupyter
) {
    function load_ipython_extension() {
        console.log(
            'This is the current notebook application instance:',
            Jupyter.notebook
        );
    }

    return {
        load_ipython_extension: load_ipython_extension
    };
});

Modifying key bindings

One of the abilities of extensions is to modify key bindings, although once again this is an API which is not guaranteed to be stable. However, custom key bindings are frequently requested, and are helpful to increase accessibility, so in the following we show how to access them.

Here is an example of an extension that will unbind the shortcut 0,0 in command mode, which normally restarts the kernel, and bind 0,0,0 in its place:

// file my_extension/main.js

define([
    'base/js/namespace'
], function(
    Jupyter
) {

    function load_ipython_extension() {
        Jupyter.keyboard_manager.command_shortcuts.remove_shortcut('0,0');
        Jupyter.keyboard_manager.command_shortcuts.add_shortcut('0,0,0', 'jupyter-notebook:restart-kernel');
    }

    return {
        load_ipython_extension: load_ipython_extension
    };
});

Note

The standard keybindings might not work correctly on non-US keyboards. Unfortunately, this is a limitation of browser implementations and the status of keyboard event handling on the web in general. We appreciate your feedback if you have issues binding keys, or have any ideas to help improve the situation.

You can see that I have used the action name jupyter-notebook:restart-kernel to bind the new shortcut. There is no API yet to access the list of all available actions, though the following in the JavaScript console of your browser on a notebook page should give you an idea of what is available:

Object.keys(require('base/js/namespace').actions._actions);

In this example, we changed a keyboard shortcut in command mode; you can also customize keyboard shortcuts in edit mode. However, most of the keyboard shortcuts in edit mode are handled by CodeMirror, which supports custom key bindings via a completely different API.

Defining and registering your own actions

As part of your front-end extension, you may wish to define actions, which can be attached to toolbar buttons, or called from the command palette. Here is an example of an extension that defines a (not very useful!) action to show an alert, and adds a toolabr button using the full action name:

// file my_extension/main.js

define([
    'base/js/namespace'
], function(
    Jupyter
) {
    function load_ipython_extension() {

        var handler = function () {
            alert('this is an alert from my_extension!');
        };

        var action = {
            icon: 'fa-comment-o', // a font-awesome class used on buttons, etc
            help    : 'Show an alert',
            help_index : 'zz',
            handler : handler
        };
        var prefix = 'my_extension';
        var action_name = 'show-alert';

        var full_action_name = Jupyter.actions.register(action, name, prefix); // returns 'my_extension:show-alert'
        Jupyter.toolbar.add_buttons_group([full_action_name]);
    }

    return {
        load_ipython_extension: load_ipython_extension
    };
});

Every action needs a name, which, when joined with its prefix to make the full action name, should be unique. Built-in actions, like the jupyter-notebook:restart-kernel we bound in the earlier Modifying key bindings example, use the prefix jupyter-notebook. For actions defined in an extension, it makes sense to use the extension name as the prefix. For the action name, the following guidelines should be considered:

  • First pick a noun and a verb for the action. For example, if the action is “restart kernel,” the verb is “restart” and the noun is “kernel”.
  • Omit terms like “selected” and “active” by default, so “delete-cell”, rather than “delete-selected-cell”. Only provide a scope like “-all-” if it is other than the default “selected” or “active” scope.
  • If an action has a secondary action, separate the secondary action with “-and-”, so “restart-kernel-and-clear-output”.
  • Use above/below or previous/next to indicate spatial and sequential relationships.
  • Don’t ever use before/after as they have a temporal connotation that is confusing when used in a spatial context.
  • For dialogs, use a verb that indicates what the dialog will accomplish, such as “confirm-restart-kernel”.

Installing and enabling extensions

You can install your nbextension with the command:

jupyter nbextension install path/to/my_extension/ [--user|--sys-prefix]

The default installation is system-wide. You can use --user to do a per-user installation, or --sys-prefix to install to Python’s prefix (e.g. in a virtual or conda environment). Where my_extension is the directory containing the Javascript files. This will copy it to a Jupyter data directory (the exact location is platform dependent - see Data files).

For development, you can use the --symlink flag to symlink your extension rather than copying it, so there’s no need to reinstall after changes.

To use your extension, you’ll also need to enable it, which tells the notebook interface to load it. You can do that with another command:

jupyter nbextension enable my_extension/main [--sys-prefix]

The argument refers to the Javascript module containing your load_ipython_extension function, which is my_extension/main.js in this example. There is a corresponding disable command to stop using an extension without uninstalling it.

Changed in version 4.2: Added --sys-prefix argument