How to create custom bokeh widgets

Custom widgets empower you to extend Bokeh’s functionality beyond built-in models by writing small TypeScript/JavaScript components that integrate seamlessly with Python. This guide walks through setting up your development environment, building a widget, packaging it, and deploying to Bokeh server or standalone documents.

1. Set Up Development Environment

  1. Install prerequisites:
    # Python environment
    pip install bokeh
    # Node.js and Yarn
    # Download from https://nodejs.org
          
  2. Clone Bokeh extension template:
    git clone https://github.com/bokeh/sample-custom-extension.git my_widget
    cd my_widget
          
    This template includes tsconfig.json, webpack setup, and Python bindings.
  3. Install JavaScript dependencies:
    cd my_widget
    yarn install
          

2. Define Python Wrapper

Create my_widget.py with a subclass of Widget that maps properties to the JS model:

from bokeh.core.properties import Int, String
from bokeh.models import Widget
from bokeh.util.compiler import TypeScript

# Load compiled TS code
ts = TypeScript("my_widget.ts")

class MyWidget(Widget):
    __implementation__ = ts

    title = String(default="My Widget")
    value = Int(default=0)
    color = String(default="blue")
    

Key points:

  • __implementation__ points to the TS file for widget logic.
  • Define Bokeh properties that synchronize Python & JS.
See also  How to Scale Bokeh Applications for Production

3. Implement TypeScript Model

Edit my_widget.ts to define the JS side:

// my_widget.ts
import {WidgetView, Widget} from "models/widgets/widget"
import * as p from "core/properties"

export class MyWidgetView extends WidgetView {
  model: MyWidget
  private _div: HTMLDivElement

  connect_signals(): void {
    super.connect_signals()
    this.connect(this.model.properties.value.change, () => this.render())
  }

  render(): void {
    if (!this._div) {
      this._div = document.createElement("div")
      this.el.appendChild(this._div)
    }
    this._div.textContent = `${this.model.title}: ${this.model.value}`
    this._div.style.color = this.model.color
    super.render()
  }
}

export namespace MyWidget {
  export type Attrs = p.AttrsOf
  export type Props = Widget.Props & {
    title: p.Property
    value: p.Property
    color: p.Property
  }
}

export interface MyWidget extends MyWidget.Attrs {}

export class MyWidget extends Widget {
  properties: MyWidget.Props
  __view_type__: MyWidgetView

  constructor(attrs?: Partial) {
    super(attrs)
  }

  static init_MyWidget(): void {
    this.prototype.default_view = MyWidgetView
    this.define({
      title: [ p.String, "My Widget" ],
      value: [ p.Number, 0 ],
      color: [ p.String, "blue" ],
    })
  }
}

MyWidget.init_MyWidget()
    

“Use connect_signals() to handle property changes and update the DOM. Define properties in init_MyWidget() for automatic syncing.”

4. Compile and Build

  1. Compile TypeScript and bundle:
    yarn build
          
  2. Install Python package in editable mode:
    pip install -e .
          
Watch for build errors in the console; missing imports or mismatched property names are common.

5. Use Custom Widget in Bokeh App

Create a Python script app.py to test your widget:

from bokeh.io import curdoc
from my_widget import MyWidget
from bokeh.layouts import column
from bokeh.models import Slider

widget = MyWidget(title="Counter", value=10, color="green")
slider = Slider(start=0, end=100, value=10, step=1, title="Adjust Value")

# Link slider to widget.value
slider.on_change("value", lambda attr, old, new: setattr(widget, "value", new))

curdoc().add_root(column(widget, slider))
    

Run the app:

bokeh serve app.py --show
  

6. Package & Distribute

Publish your widget as a Python package and npm module:

  • Update setup.py and package.json metadata.
  • Upload Python package to PyPI: twine upload dist/*
  • Publish JS package to npm: npm publish
Ensure __implementation__ references compiled JS once distributed.

7. Advanced Techniques

  • Custom Events: Use this.model.trigger_event() and model.stream() for real-time interactions.
  • External Libraries: Integrate D3.js or Leaflet by importing in TS and manipulating DOM.
  • Testing: Write unit tests for Python and JS components using pytest and Jest.

Quick Reference: Files & Commands

File Purpose Command
my_widget.py Python wrapper defining properties
my_widget.ts TypeScript model & view logic
package.json JS dependencies & build scripts yarn build
setup.py Python packaging pip install -e .
app.py Demo Bokeh application bokeh serve app.py