JupyterLab Extension Examples

repository·main·Indexed 19 days ago

https://github.com/jupyterlab/extension-examples

A collection of tutorial series and practical guides for developing JupyterLab extensions. Includes examples for adding buttons to the cell toolbar, creating cross-compatible extensions for JupyterLab and Jupyter Notebook 7+, integrating CodeMirror extensions via IEditorExtensionRegistry, and adding commands to the Command Palette. Provides instructions for setting up integration testing using Playwright and Galata.

Tokens
72K
Snippets
211
Records
260
Agent score
66%

What's inside jupyterlab-extension-examples

  1. Explore JupyterLab extension examples by topic

    main

    This repository provides a collection of practical examples to help developers learn how to create JupyterLab extensions. Instead of relying solely on documentation, you can study specific implementations for various extension points.

    Key topics covered include:

    • UI Elements: Cell toolbar buttons, Command Palette registration, Context Menus, Main Menus, and custom Widgets.
    • Core Functionality: Commands, Completer (autocomplete) customization, Kernel messaging/output, and MIME renderers.
    • Advanced Integration: React widgets, Server extensions (backend + frontend), State persistence, and Signal-based communication between widgets.
    • Dual Compatibility: Techniques for designing extensions that work in both JupyterLab and Jupyter Notebook v7+.

    Each example typically includes a functional explanation, visual previews, a list of used JupyterLab APIs/extension points, and code snippets illustrating the internal logic.

  2. How IEditorExtensionRegistry works

    main

    The IEditorExtensionRegistry is the central interface for managing CodeMirror extensions within JupyterLab. It allows developers to register new features that modify the behavior or appearance of CodeMirror editors (used for cells and text files).

    Key capabilities:

    • addExtension(options): Registers a new extension with a unique name, a factory for instantiation, and optional configuration schemas.
    • createConfigurableExtension(callback): A helper method used within the extension factory to handle parameterized extensions. The provided callback receives the current configuration values and must return the CodeMirror Extension. This ensures the editor updates dynamically when user settings change.
  3. How to create a cross-compatible extension for JupyterLab and Jupyter Notebook

    main

    To create an extension that works in both JupyterLab and Jupyter Notebook 7+, you can define multiple plugins within the same package. Each plugin specifies which environment it targets by using specific requirement tokens in its requires array.

    • For JupyterLab, require the ILabShell token.
    • For Jupyter Notebook 7+, require the INotebookShell token.

    When the application starts, it checks the required tokens. If the token is available in the current environment, the plugin's activate method is called. The activate method receives the requested shell token as an argument, which you can use to manipulate the UI (e.g., adding buttons to the top area in JupyterLab or the right sidebar in Notebook).

    For more advanced scenarios, refer to the Extension Compatibility Guide.

    // Example of targeting JupyterLab
    const pluginJupyterLab: 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) => {
        // Implementation
      }
    };
    
    // Example of targeting Jupyter Notebook
    const pluginJupyterNotebook: 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) => {
        // Implementation
      }
    };
  4. Understand the DocumentWidget structure

    main

    The DocumentWidget is the primary view that opens when a file is accessed. It is composed of several key attributes that manage the lifecycle and UI of the document:

    • context: The bridge between the file on disk and its content. It contains the DocumentModel and the sessionContext (which handles backend communication).
    • title: Manages the content displayed in the tab.
    • toolbar: The editor's toolbar used to trigger actions.
    • contentHeader: A panel located between the toolbar and the main content area, useful for notifications or secondary toolbars.
    • content: The main area where the document's specific view is rendered.
  5. Core components of a MIME renderer extension

    main

    A MIME renderer extension in JupyterLab typically consists of three main architectural components defined in the entry point (usually src/index.ts):

    1. VideoWidget (or similar Widget class): A class that inherits from Widget. It is responsible for taking the MIME type data and rendering it into an HTML DOM node (e.g., creating a <video> element).
    2. rendererFactory: An object that implements the logic to create new instances of your Widget class. It acts as the bridge between the JupyterLab application and your custom renderer.
    3. extension plugin object: The main entry point. It contains the metadata required by JupyterLab to load the extension, including supported file types and MIME types.
  6. Implement conditional fields in metadata forms

    main

    To show or hide fields based on the value of other metadata, use JSON Schema's if-then-else logic within an allOf block. This allows you to create dynamic forms where certain properties only appear when a specific condition is met (e.g., a checkbox is checked).

    "allOf": [
      {
        "if": {
          "properties": {
            "/my-extension/active": {
              "const": true
            }
          }
        },
        "then": {
          "properties": {
            "/my-extension/conditional": {
              "title": "Conditional field",
              "description": "Field depend on the value of the 'Custom widget'",
              "type": "string",
              "enum": ["condition1", "condition2", "condition3"]
            }
          }
        }
      }
    ]
  7. How to define extension settings using JSON Schema

    main

    Extension settings are defined using a JSON Schema. This schema allows you to specify properties (like integers or booleans) and metadata like title and description which appear in the JupyterLab Advanced Settings editor.

    JupyterLab also supports special keys like jupyter.lab.menus to allow extensions to inject UI elements (like menu items) directly from the settings schema.

    Naming Convention Requirements: To ensure JupyterLab correctly maps your settings, you must follow a strict naming convention:

    1. Plugin ID: Must be structured as package-name:settings-name (e.g., @jupyterlab-examples/settings:settings-example).
    2. Settings File: Must be named exactly after the settings-name part (e.g., settings-example.json).
    3. Package Configuration: The folder containing the schema must be specified in package.json under the jupyterlab.schemaDir key.
    // schema/settings-example.json
    {
      "title": "Settings Example",
      "description": "Settings of the settings example.",
      "properties": {
        "limit": {
          "type": "integer",
          "title": "Limit",
          "default": 25
        },
        "flag": {
          "type": "boolean",
          "title": "Simple flag",
          "default": false
        }
      },
      "type": "object"
    }
  8. How documents work in JupyterLab

    main

    In JupyterLab, a document is a file stored on disk. The system uses several abstractions to bridge the gap between the file on disk and the user interface:

    • Context: The bridge between file metadata (like the file path) and its model.
    • IModel: Represents the actual file content and handles data logic.
    • IDocumentWidget: The visual view of the model that users interact with in the frontend.

    To support a new document type, a developer must define three elements:

    1. A Model: Implements IModel and contains the content in a sharedModel (required for collaboration).
    2. A View: A Widget (implementing IDocumentWidget) for user interaction.
    3. A File Type: A dictionary of attributes mapping mimetypes to specific models and views.

    Registration is handled via the DocumentRegistry using factories for models (IModelFactory) and views (IWidgetFactory).

  9. Use the DocumentModel to manage file content

    main

    The DocumentModel represents the file content in the frontend. It allows you to monitor the state of the file (e.g., checking the dirty property to see if content differs from the disk) and modify the content.

    For most file types (excluding notebooks), content is loaded and saved to disk as a string using the following methods:

    • toString(): Converts the content to a string (for non-textual files, this is a base-64 representation).
    • fromString(value): Loads content from a string.
  10. Use optional dependencies to support multiple environments

    main

    When building JupyterLab extensions, you can define dependencies as optional in your JupyterFrontEndPlugin configuration. This is useful when your extension provides features for specific UI elements (like the Launcher or Command Palette) that might not exist in all JupyterLab environments.

    If an application cannot provide an optional interface, the corresponding argument in your activate function will be null. You should always check for null before interacting with these interfaces.

    const extension: JupyterFrontEndPlugin<void> = {
      id: '@jupyterlab-examples/launcher:plugin',
      description: 'A minimal JupyterLab example using the launcher.',
      autoStart: true,
      requires: [IFileBrowserFactory],
      optional: [ILauncher, ICommandPalette],
      activate: (
        app: JupyterFrontEnd,
        browserFactory: IFileBrowserFactory,
        launcher: ILauncher | null,
        palette: ICommandPalette | null
      ) => {
        // ...
      }
    };
  11. Display Kernel Output using OutputArea and OutputAreaModel

    main

    To render kernel messages (like stdout, stderr, or rich media) in a UI, use the OutputArea and OutputAreaModel pattern.

    • OutputAreaModel: Holds the data/state of the output.
    • SimplifiedOutputArea (or OutputArea): A Widget that renders the data from the model. It can display everything from raw text to interactive ipywidgets.
    • Execution: Use the static OutputArea.execute method to send code to a kernel via a SessionContext. This method handles the communication and ensures the results are rendered in the provided widget.

    To display the output, you must add the OutputArea widget to a parent container (like a Panel) using addWidget().

    // Create the model and the widget
    this._outputareamodel = new OutputAreaModel();
    this._outputarea = new SimplifiedOutputArea({
      model: this._outputareamodel,
      rendermime: rendermime
    });
    
    // Add the widget to the UI panel
    this.addWidget(this._outputarea);
    
    // Execute code and display results
    execute(code: string): void {
      OutputArea.execute(code, this._outputarea, this._sessionContext)
        .then((msg: KernelMessage.IExecuteReplyMsg | undefined) => {
          console.log(msg);
        })
        .catch(reason => console.error(reason));
    }