Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ subtrees:
- entries:
- file: plugins/testing_and_publishing/test
- file: plugins/testing_and_publishing/deploy
- file: plugins/testing_and_publishing/hub_customization
- file: plugins/testing_workshop_docs/index
subtrees:
- entries:
Expand Down
30 changes: 9 additions & 21 deletions docs/plugins/testing_and_publishing/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,14 @@ template, this has already been done for you.)

Once your package is listed on [PyPI] (and includes the `Framework :: napari`
[classifier]), it will also be visible on the [napari
hub](https://napari-hub.org/). To ensure you are providing the relevant metadata and
description for your plugin, see the following documentation in the [napari hub
wiki](https://github.com/chanzuckerberg/napari-hub/wiki/Plugin-Developer's-Guide-to-the-napari-hub):

- [Customizing your plugin’s
listing](https://github.com/chanzuckerberg/napari-hub/wiki/Customizing-your-plugin's-listing)
- [Writing the perfect description for your
plugin](https://github.com/chanzuckerberg/napari-hub/wiki/Writing-the-Perfect-Description-for-your-Plugin)

```{admonition} The hub
For more about the napari hub, see the [napari hub About
page](https://www.napari-hub.org/about). To learn more about the hub’s
development process, see the [napari hub GitHub’s
Wiki](https://github.com/chanzuckerberg/napari-hub/wiki).

If you want your plugin to be available on PyPI, but not visible on the napari
hub, add a `visibility` flag to your plugin manifest. For more details, see the
[manifest reference](plugin-manifest) and napari hub [customization
guide](https://github.com/chanzuckerberg/napari-hub/wiki/Customizing-your-plugin's-listing#visibility).
```
hub](https://napari-hub.org/).

To ensure you are providing the best metadata and description for your plugin,
see our comprehensive guide: [](hub-customization). This guide covers:

- Setting package metadata in `pyproject.toml`
- Using npe2 manifest metadata for hub display
- Controlling plugin visibility

## Deployment

Expand Down Expand Up @@ -57,4 +45,4 @@ forum](https://forum.image.sc/tag/napari).
[autodeploy]: https://github.com/napari/napari-plugin-template#set-up-automatic-deployments
[classifier]: https://pypi.org/classifiers/
[pypi]: https://pypi.org/
[pypi-upload]: https://packaging.python.org/en/latest/tutorials/packaging-projects/#uploading-the-distribution-archives
[pypi-upload]: https://packaging.python.org/en/latest/tutorials/packaging-projects/#uploading-the-distribution-archives
321 changes: 321 additions & 0 deletions docs/plugins/testing_and_publishing/hub_customization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
(hub-customization)=

# Customizing your plugin's listing on napari hub

Once your plugin is published to [PyPI](https://pypi.org/) with the `Framework :: napari` classifier, it will automatically appear on the [napari hub](https://napari-hub.org/). This guide explains how to customize your plugin's listing to provide the best experience for potential users.

## Overview

The napari hub displays information about your plugin from three main sources:

1. **PyPI** - Core package metadata from your `pyproject.toml`
2. **README** - As defined in your `pyproject.toml`, serves as the description on your plugin's detail page.
3. **npe2 manifest** - Plugin-specific metadata from your `napari.yaml` file

By understanding how these sources work together, you can ensure your plugin appears with accurate, appealing information on the hub.

## Setting package metadata in pyproject.toml

Most of the information displayed on the napari hub comes from your [Python package metadata](https://packaging.python.org/en/latest/specifications/core-metadata/). The napari hub reads this information from [PyPI's JSON API](https://docs.pypi.org/api/json/) after you publish your package.

### Essential fields

These fields appear prominently on your plugin's listing and in search results:

#### Name

The package name as it appears on PyPI. This is what users will use to install your plugin.

```toml
[project]
name = "napari-example-plugin"
```

#### Summary

A one-line description of your plugin. Keep it concise and descriptive - it appears in plugin listings and search results.

```toml
[project]
description = "A plugin for advanced image segmentation using deep learning"
```

#### Version

The current version of your plugin. We strongly recommend using [Semantic Versioning (SemVer)](https://semver.org/) or [Intended Effort Versioning (EffVer)](https://jacobtomlinson.dev/effver/) with Python conventions for pre-releases ([PEP 440](https://www.python.org/dev/peps/pep-0440/)).

```toml
[project]
version = "0.1.0"
```

````{tip}
If you're using dynamic versioning tools like `setuptools-scm` or `hatch-vcs`, you can specify:

```toml
[project]
dynamic = ["version"]
```
````

#### Authors

Plugin authors displayed on your listing. The hub will display this information on the search page and on your plugin's page.

```toml
[project]
authors = [
{name = "Jane Doe", email = "jane@example.com"},
{name = "John Smith"},
]
```

#### License

The license under which your plugin is distributed. Use a valid [SPDX identifier](https://spdx.org/licenses/) or the string `"Other"`. Modern Python packaging uses the [`license-expression` format](https://packaging.python.org/en/latest/specifications/core-metadata/#license-expression) for specifying licenses.

```toml
[project]
license = "BSD-3-Clause"
Comment thread
DragaDoncila marked this conversation as resolved.
license-files = ["LICENSE"]
```

````{note}
Previously, the valid way to specify license information used the `license` table with `file` or `text` keys and a license trove classifier. This still works for listings on the napari-hub, but is deprecated in favor of `license-expression`.

```toml
[project]
license = {text = "BSD-3-Clause"}
```

````

#### Classifiers

[PyPI Trove classifiers](https://pypi.org/classifiers/) provide structured metadata about your plugin. The `Framework :: napari` classifier is required for hub visibility.

```toml
[project]
classifiers = [
"Development Status :: 4 - Beta",
"Framework :: napari",
"Intended Audience :: Science/Research",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: Image Processing",
]
```

#### Python version requirements

Additionally, specify the Python versions your plugin supports. If you specify `">=3.10"`, the hub will tag your plugin as supporting Python 3.10, 3.11, 3.12, etc.

```toml
[project]
requires-python = ">=3.10"
```

#### Dependencies

List your plugin's dependencies. The hub displays these on the detail page.

```toml
[project]
dependencies = [
"numpy>=1.21",
"scikit-image>=0.19",
"torch>=2.0",
"qtpy",
]
```

```{warning}
**Never include Qt backends** (`PyQt5`, `PyQt6`, `PySide2`, `PySide6`) or `napari[all]` in your base dependencies! See [](best-practices-no-qt-backend) for details.
```

### Project URLs

It is best practice to specify the following URLs in your `pyproject.toml` file:

```toml
[project.urls]
Homepage = "https://github.com/username/napari-example-plugin"
Comment thread
DragaDoncila marked this conversation as resolved.
Documentation = "https://napari-example-plugin.readthedocs.io"
"Source Code" = "https://github.com/username/napari-example-plugin"
"Bug Tracker" = "https://github.com/username/napari-example-plugin/issues"
"User Support" = "https://forum.image.sc/tag/napari"
```

The hub displays your project's PyPI link (not specified here, created when you publish your package),
and your project's GitHub link on your plugin details page. The GitHub link will be parsed from
your `Homepage` **or** `Source Code` links under `[project.urls]`, so you can update your `Homepage`
to a dedicated website if you have one.

### Complete example

Here's a complete example of a well-configured `pyproject.toml`:

```toml
[project]
name = "napari-example-plugin"
version = "0.1.0"
description = "Advanced image segmentation using deep learning"
readme = "README.md"
license = "BSD-3-Clause"
license-files = ["LICENSE"]
requires-python = ">=3.10"
authors = [
{name = "Jane Doe", email = "jane@example.com"},
]
classifiers = [
"Development Status :: 4 - Beta",
"Framework :: napari",
"Intended Audience :: Science/Research",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Image Processing",
]
dependencies = [
"numpy>=1.21",
"scikit-image>=0.19",
"magicgui>=0.8.0",
]

[project.urls]
Homepage = "https://github.com/username/napari-example-plugin"
Documentation = "https://napari-example-plugin.readthedocs.io"
"Bug Tracker" = "https://github.com/username/napari-example-plugin/issues"
"User Support" = "https://forum.image.sc/tag/napari"

[project.entry-points."napari.manifest"]
napari-example-plugin = "napari_example_plugin:napari.yaml"
```

## Plugin Hub Description with README.md

A detailed description of your plugin. This appears on the plugin's detail page and is indexed for search. Use your `README.md` file:

```toml
[project]
readme = "README.md"
```

The hub will render your README with proper Markdown formatting. If you begin with a Level 1 heading, it will be treated as a title and removed from the description.

### Writing a quality Readme

- **Clear summary**: Start with who the plugin is for, what data it works with, and what problems it solves
- **Quick start example**: Include images, GIFs, or videos showing the plugin in action
- **Relevant keywords**: Mention key terms users might search for (e.g., "segmentation", "3D", "time series")
- **Section headings**: Use Level 2 headings (`##`) to organize content - these create navigation links in the sidebar

### Including images and media

There are a few ways to include images and other media in your description, but these assets need to be hosted somewhere and accessible via an absolute URL. In other words, relative paths to images in your repository will not work. Images should use the markdown format `![alt text](https://absolute.url/to/image)`. Consider how an image located on the `main` branch at `./resources/image.png` in your repository could be hosted:

1. The raw path to the image, identified with `raw.githubusercontent.com`. e.g. `![raw image](https://raw.githubusercontent.com/user/repo/main/resources/image.png)`. On Github, you can open the image in the repo, open the image in a new tab and copy the address bar url.
2. The raw blob path to the image, with `?raw=true` appended. e.g. `![blob image](https://github.com/user/repo/blob/main/resources/image.png?raw=true)`. On Github, you can open the image in the repo and right-click the image to copy the link address.
3. The raw path to any image or asset on Github or on any other site, including your own static documentation.

## npe2 manifest metadata

Your plugin's [npe2 manifest](plugin-manifest) (`napari.yaml`) provides napari-specific metadata that appears on the hub.

### Display name

The `display_name`, and not the `name`, appears in plugin listings and the napari plugin manager:

```yaml
name: napari-example-plugin
display_name: Example Segmentation Plugin
```

### Plugin type indicators

The hub automatically detects your plugin's capabilities from your manifest contributions and displays them as Plugin types:

- **Reader plugins**: Detected from `contributions.readers`
- **Writer plugins**: Detected from `contributions.writers`
- **Widget plugins**: Detected from `contributions.widgets`
- **Sample data**: Detected from `contributions.sample_data`

### Reader and writer file extensions

The hub displays supported file extensions for readers and writers:

```yaml
contributions:
readers:
- command: napari-example-plugin.read_tiff
filename_patterns: ["*.tif", "*.tiff"]
- command: napari-example-plugin.read_custom
filename_patterns: ["*.custom"]

writers:
- command: napari-example-plugin.write_tiff
filename_patterns: ["*.tif"]
layer_types: ["image", "labels"]
```

### Visibility control

Control whether your plugin appears in hub search and listings, and in the napari plugin manager:

```yaml
name: napari-example-plugin
visibility: public # or "hidden"
```

- `public` (default): Plugin appears in search and listings
- `hidden`: Detail page is accessible via direct link, but plugin doesn't appear in search. Remains installable via napari plugin manager.

## Troubleshooting

### Removing your plugin from napari and the hub

To completely hide your plugin from all napari-related APIs, workflows and listings, remove the `Framework :: napari` classifier from your `pyproject.toml`:

```toml
[project]
classifiers = [
# "Framework :: napari", # Remove or comment out this line
"Programming Language :: Python :: 3",
# ... other classifiers
]
```

```{important}
You must release a new version for this change to take effect.
```

After removing the classifier:

- Your plugin will still work when manually installed (e.g. `pip install your-plugin`)
- It won't appear in the napari plugin manager
- It won't appear on the napari hub
- It won't be automatically discovered by napari metadata tools

### My changes aren't showing up on the hub

The napari hub updates plugin information periodically. After publishing a new version to PyPI:

1. Wait up to 4 hours for the hub to refresh
2. Clear your browser cache
3. Check that your new version appears on [PyPI](https://pypi.org/)

### My plugin doesn't appear on the hub at all

Check that:

- Your package is published to PyPI
- You included the `Framework :: napari` classifier
- Your plugin's `visibility` is not set to `hidden` (or is set to `public`)
- Your package has a valid npe2 manifest (`napari.yaml`) with a `napari.manifest` entry point
11 changes: 10 additions & 1 deletion docs/plugins/testing_and_publishing/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Testing your plugin is an important step to ensure that it works as expected and to avoid breaking changes in the future.
Once your plugin is tested and ready to go, you can publish it to any combination of PyPI, conda-forge, and the napari-hub.

````{grid}
````{grid} 2
```{grid-item-card} Testing guidelines
:link: plugin-test
:link-type: ref
Expand All @@ -21,6 +21,15 @@ The steps to publish your plugin to PyPI, conda-forge, and the napari-hub.
```
````

````{grid}
```{grid-item-card} Customizing your napari hub listing
:link: hub-customization
:link-type: ref

Learn how to customize your plugin's appearance on the napari hub with metadata, descriptions, and configuration files.
```
````

````{grid}
```{grid-item-card} Workshop on testing plugins
:link: in-depth-guide-to-plugin-testing
Expand Down