Building Custom JupyterLab Extensions: A Developer's Guide
Step-by-step tutorial on creating your first JupyterLab extension, from setup to publishing on npm.
Building Custom JupyterLab Extensions: A Developer's Guide
Creating custom JupyterLab extensions can significantly enhance your team's productivity. This comprehensive guide will walk you through building your first extension from scratch.
Prerequisites
Before starting, ensure you have:
- Node.js (version 14 or higher)
- Python (version 3.8 or higher)
- JupyterLab installed
- Basic knowledge of TypeScript and React
Setting Up Your Development Environment
1. Install Required Tools
pip install jupyterlab cookiecutter
npm install -g yarn
2. Create Extension Template
cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts
This will prompt you for:
- Extension name
- Author information
- License type
- Python package name
3. Development Installation
cd your-extension-name
pip install -e .
jupyter labextension develop . --overwrite
Understanding the Extension Structure
A typical JupyterLab extension includes:
your-extension/
āāā src/
ā āāā index.ts # Main entry point
ā āāā widget.tsx # React components
āāā package.json # NPM configuration
āāā pyproject.toml # Python configuration
āāā install.json # JupyterLab configuration
āāā tsconfig.json # TypeScript configuration
Building Your First Extension
Let's create a simple "Hello World" extension that adds a new menu item.
1. Update the Main Index File
// src/index.ts
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ICommandPalette } from '@jupyterlab/apputils';
const plugin: JupyterFrontEndPlugin<void> = {
id: 'hello-world:plugin',
autoStart: true,
requires: [ICommandPalette],
activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
const { commands } = app;
const command = 'hello-world:open';
commands.addCommand(command, {
label: 'Say Hello',
caption: 'Display a hello message',
execute: () => {
console.log('Hello from JupyterLab extension!');
}
});
palette.addItem({ command, category: 'Tutorial' });
}
};
export default plugin;
2. Build and Test
jlpm build
jupyter lab build --dev-build=False --minimize=False
Start JupyterLab and look for your command in the command palette (Ctrl+Shift+C).
Advanced Extension Features
Adding UI Components
Create interactive widgets using React:
// src/widget.tsx
import React, { useState } from 'react';
import { ReactWidget } from '@jupyterlab/apputils';
const HelloWidget = () => {
const [count, setCount] = useState(0);
return (
<div style={{ padding: '20px' }}>
<h2>Hello JupyterLab!</h2>
<p>Button clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me!
</button>
</div>
);
};
export class HelloReactWidget extends ReactWidget {
render(): JSX.Element {
return <HelloWidget />;
}
}
Integrating with Notebook Cells
Access and manipulate notebook content:
import { INotebookTracker } from '@jupyterlab/notebook';
const processNotebook = (tracker: INotebookTracker) => {
const notebook = tracker.currentWidget;
if (!notebook) return;
const cells = notebook.content.widgets;
cells.forEach((cell, index) => {
console.log(`Cell ${index}:`, cell.model.value.text);
});
};
Adding Settings
Allow users to configure your extension:
// schema/plugin.json
{
"jupyter.lab.setting-icon": "ui-components:palette",
"jupyter.lab.setting-icon-label": "Hello World Settings",
"title": "Hello World",
"description": "Settings for the Hello World extension",
"type": "object",
"properties": {
"greeting": {
"type": "string",
"title": "Greeting Message",
"description": "Custom greeting message",
"default": "Hello"
}
}
}
Testing Your Extension
Unit Testing
// tests/hello.spec.ts
import { test, expect } from '@playwright/test';
test('hello world extension loads', async ({ page }) => {
await page.goto('/lab');
await page.waitForSelector('[data-id="hello-world:plugin"]');
expect(await page.isVisible('[data-id="hello-world:plugin"]')).toBe(true);
});
Manual Testing
- Install in development mode
- Test all features thoroughly
- Check browser console for errors
- Verify compatibility with different JupyterLab versions
Publishing Your Extension
1. Prepare for Release
# Update version
npm version patch
# Build production version
jlpm build:prod
2. Publish to PyPI and NPM
# Build Python package
python -m build
# Upload to PyPI
twine upload dist/*
# Publish to NPM
npm publish
3. Add to conda-forge (optional)
Submit a PR to conda-forge for easier installation.
Best Practices
Code Quality
- Use TypeScript for type safety
- Follow JupyterLab's coding standards
- Add comprehensive tests
- Document your API
User Experience
- Provide clear error messages
- Add helpful tooltips and documentation
- Follow JupyterLab's UI patterns
- Support keyboard shortcuts
Performance
- Lazy load heavy components
- Debounce expensive operations
- Minimize bundle size
- Cache computed values
Common Pitfalls
- Not handling async operations properly
- Forgetting to dispose of event listeners
- Ignoring accessibility requirements
- Poor error handling
- Not testing across different environments
Resources for Further Learning
- JupyterLab Extension Developer Guide
- JupyterLab GitHub Repository
- Extension Examples
- JupyterLab Extension Template
Building JupyterLab extensions opens up endless possibilities for customizing and enhancing your data science workflow. Start small, iterate quickly, and don't hesitate to contribute back to the community!
Ready to transform your notebooks?
Try Auto Dashboards and start creating interactive dashboards from your Jupyter notebooks with just one click.