Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

4. From Script to Plugin

Block 4: From Script to Plugin

Goal: Package the interactive spot detection widget from Block 3 into a pip-installable napari plugin, then install and test it.

The primary steps in making a napari plugin are:

  1. Choose which manifest contribution(s) your plugin requires

  2. Create your repository using the napari-plugin-template

  3. Implement your contributions

  4. Share your plugin with the community

A functional napari plugin only needs 4 files to be shared: napari.yaml, some Python module, pyproject.toml, and README.md. The napari-plugin-template generates all of these — plus testing, CI, and documentation scaffolding — so you can focus on the code.

1. What is a napari plugin? (5 min)

A napari plugin is a Python package that declares contributions in a napari.yaml manifest file. napari reads this manifest to discover what your plugin provides without importing your code at startup. The manifest itself is registered as an entry point in pyproject.toml. To learn about the minimal requirements of a napari plugin, you can read through the Your first plugin tutorial. However, for this workshop we will use the napari-plugin-template.

napari plugins can be found on napari-hub.org. The hub is automatically updated when a plugin is published to PyPI, though many plugins are also available on conda-forge. While we won’t deploy our plugin in this workshop, you can learn more by reading the plugin deploy instructions

Contribution types

A contribution is a construct in napari.yaml (the manifest file), that napari uses for each specific type of plugin. Each contribution conforms to a function signature, i.e. the function linked to the contribution defines what napari provides to the plugin (e.g., data and parameters) and what the plugin returns to napari. napari is then able to use the functions pointed to in napari.yaml to carry out the plugin tasks. Please see the contributions guide for more details. (And technical references for the manifest and contributions. Many plugins will declare multiple contributions to provide all of the desired functionality.

TypeWhat it enables
ReaderOpen file formats napari doesn’t know
WriterSave layers to custom formats
WidgetAdd GUI panels for analysis, measurement, etc.
Sample dataProvide built-in example datasets
ThemeCustomize the viewer’s appearance

Today we’ll make a widget plugin — the detect_spots function from Block 3, packaged so anyone can install it and use it in napari.

2. Scaffolding with napari-plugin-template (15 min)

The napari-plugin-template uses Copier to generate a complete plugin project structure from a few prompts.

Run the template

Open a terminal and navigate to where you want your plugin; the following commands do not require the environments from this workshop, and are fully self-contained. Then run the pixi command, replacing <new-plugin-name> with your desired plugin name (e.g. napari-spot-detector):

pixi exec -w npe2 -w jinja2-time -w python=3.13 copier copy --trust https://github.com/napari/napari-plugin-template <new-plugin-name>

Alternatively, uv:

uvx -w jinja2-time -w npe2 -p 3.13 copier copy --trust https://github.com/napari/napari-plugin-template <new-plugin-name>

Template prompts

You’ll be asked a series of questions. When prompted for which plugins to include, you only need to answer Yes to Include widget plugin?, but you may be interested in exploring the other contributions as well. To read more about the prompts, you can refer to the napari-plugin-template Prompts Reference

PromptAnswer
plugin_namenapari-spot-detector
display_nameanything
module_namenapari_spot_detector
short_descriptionanything
project infoas appropriate
include_reader_pluginoptional
include_writer_pluginoptional
include_sample_data_pluginoptional
include_widget_pluginYes
Other defaultsPress Enter to accept

After completing all of the questions, a directory will be created containing your new napari plugin. You will be given instructions on how to upload the initialized git repository to GitHub. By default, we will not be covering this aspect in the tutorial, but please feel free to ask the teaching team if you would like to give it a try.

Structure and first steps

Now, we’ll explore the generated project structure. An up-to-date reference is available in the napari-plugin-template README.

See below for explanations about some of the most notable files, but do not hesitate to reach out to the teaching team if you have questions about any of the other files.

3. Understanding napari.yaml (10 min)

Open src/napari_spot_detector/napari.yaml. This is the manifest — the heart of your plugin:

name: napari-spot-detector
display_name: napari-spot-detector
contributions:
  commands:
    - id: napari-spot-detector.make_function_widget
      python_name: napari_spot_detector._widget:threshold_autogenerate_widget
      title: Make threshold widget
  widgets:
    - command: napari-spot-detector.make_function_widget
      autogenerate: true
      display_name: Threshold

Key concepts:

The pyproject.toml has an entry point that tells napari where to find the manifest:

[project.entry-points."napari.manifest"]
napari.manifest = "napari_spot_detector:napari.yaml"

Note that src doesn’t appear in the path napari_spot_detector:napari.yaml, but napari.yaml is definitely inside the src/ folder. Python knows to look there because pyproject.toml declares:

[tool.setuptools.packages.find]
where = ["src"]

4. Implementing the widget (20 min)

Now let’s add our spot detection logic. Open src/napari_spot_detector/_widget.py.

The template populates _widget.py with four example widget approaches:

  1. Autogenerated function — a plain function with type annotations; napari uses magicgui to auto-generate GUI widgets from the annotations.

  2. @magic_factory decorator — gives you control over individual widget parameters (slider ranges, step sizes, etc.) while keeping things simple.

  3. magicgui.widgets.Container subclass — more flexibility while still using magicgui’s type-annotation-based widget creation.

  4. QWidget subclass — full control over layout, callbacks, and events.

We’ll use option 2 (@magic_factory) — the sweet spot of control and simplicity for our spot detection function.

Step 1: Add imports

import numpy as np
from scipy import ndimage as ndi
from skimage.feature import blob_log
from napari.types import ImageData, LayerDataTuple

Step 2: Add the spot detection function

Add our detect_spots function from Block 3, switching from @magicgui to @magic_factory so napari can call it from the manifest:

@magic_factory(
    auto_call=True,
    high_pass_sigma={"widget_type": "FloatSlider", "min": 0, "max": 20},
    spot_threshold={"widget_type": "FloatSlider", "min": 0.01, "max": 1.0, "step": 0.01},
    blob_sigma={"widget_type": "FloatSlider", "min": 1, "max": 20},
)
def detect_spots(
    image: ImageData,
    high_pass_sigma: float = 2.0,
    spot_threshold: float = 0.1,
    blob_sigma: float = 5.0,
) -> LayerDataTuple:
    """Detect spots in an image using Laplacian of Gaussian.

    Parameters
    ----------
    image : np.ndarray
        The image in which to detect spots.
    high_pass_sigma : float
        Sigma for the background-suppressing high-pass filter.
    spot_threshold : float
        Relative threshold for spot detection (lower = more spots).
    blob_sigma : float
        Expected spot size — passed as max_sigma to the detector.
    """
    # Suppress background with gaussian high-pass
    low_pass = ndi.gaussian_filter(image, high_pass_sigma)
    filtered = (image - low_pass).clip(0)

    # Detect spots with Laplacian of Gaussian
    blobs = blob_log(
        filtered,
        max_sigma=blob_sigma,
        threshold=None,
        threshold_rel=spot_threshold,
    )

    # Convert to points: first two columns are y, x coordinates
    coords = blobs[:, :2]
    # Third column is the detected sigma — convert to diameters for sizing
    sizes = 2 * np.sqrt(2) * blobs[:, 2]

    return (coords, {"name": "detected_spots","size": sizes, "face_color": "yellow"}, "Points")

Step 3: Update napari.yaml

Add a new command and widget entry for our function:

contributions:
  commands:
    - id: napari-spot-detector.make_detect_spots_widget
      python_name: napari_spot_detector._widget:detect_spots
      title: Make spot detection widget
  widgets:
    - command: napari-spot-detector.make_detect_spots_widget
      display_name: Detect Spots
  menus:
    napari/layers/analyze:
      - command: napari-spot-detector.make_detect_spots_widget

5. Install and test (15 min)

Install the plugin

A single command will get you started from the root of the plugin: uv run will install the plugin in editable mode with the dev dependency-group:

cd napari-spot-detector
uv run napari

For a more classical approach, you could personally create a virtual environment, activate it, and install the plugin in editable mode with:

cd napari-spot-detector
uv venv -p 3.13
.venv\Scripts\activate  # Windows
source .venv/bin/activate  # macOS/Linux

uv pip install -e . --group dev

The -e flag installs in editable mode — any changes you make to the source code take effect immediately after restarting napari. To launch:

napari

Test in napari

From the menu: Plugins > napari-spot-detector > Detect Spots

Or find it at: Layers > Analyze > Detect Spots

Test with pytest

The template already includes a test file. Run it with pytest:

cd napari-spot-detector
uv run pytest

6. Publishing overview (5 min)

To share your plugin with the world:

1. Push to GitHub

git remote add origin https://github.com/YOUR_USERNAME/napari-spot-detector.git
git push -u origin main

2. Publish to PyPI

The template includes a GitHub Actions workflow (.github/workflows/test_and_deploy.yml) that automatically publishes to PyPI when you push a version tag. The actual version number of the published package is derived from git tags via setuptools_scm.

git tag v0.1.0
git push --tags

Or create a GitHub Release with a new tag:

3. Appear on napari hub

Once published on PyPI, your plugin will automatically appear on napari-hub.org after a short delay.

Bonus exercises

Finished early? Here are some ways to extend your plugin:

Recap

In this block you:

StepWhat you did
1Learned about plugin contribution types
2Scaffolded a plugin project with copier
3Understood napari.yaml manifest structure
4Added your spot detection function to _widget.py
5Installed in editable mode and tested
6Learned how to publish to PyPI and the napari hub

Want to contribute?

Have you enjoyed your experience using pixi or uv? We’d love to update the napari-plugin-template to first class these tools!