Files
navidrome/plugins/examples
Deluan Quintão f1e75c40dc feat(plugins): add JSONForms-based plugin configuration UI (#4911)
* feat(plugins): add JSONForms schema for plugin configuration

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance error handling by formatting validation errors with field names

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enforce required fields in config validation and improve error handling

Signed-off-by: Deluan <deluan@navidrome.org>

* format JS code

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add config schema validation and enhance manifest structure

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: refactor plugin config parsing and add unit tests

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add config validation error message in Portuguese

* feat: enhance AlwaysExpandedArrayLayout with description support and improve array control testing

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: update Discord Rust plugin configuration to use JSONForm for user tokens and enhance schema validation

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: resolve React Hooks linting issues in plugin UI components

* Apply suggestions from code review

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* format code

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: migrate schema validation to use santhosh-tekuri/jsonschema and improve error formatting

Signed-off-by: Deluan <deluan@navidrome.org>

* address PR comments

Signed-off-by: Deluan <deluan@navidrome.org>

* fix flaky test

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance array layout and configuration handling with AJV defaults

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement custom tester to exclude enum arrays from AlwaysExpandedArrayLayout

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add error boundary for schema rendering and improve error messages

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: refine non-enum array control logic by utilizing JSONForms schema resolution

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add error styling to ToggleEnabledSwitch for disabled state

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: adjust label positioning and styling in SchemaConfigEditor for improved layout

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement outlined input controls renderers to replace custom fragile CSS

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: remove margin from last form control inside array items for better spacing

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance AJV error handling to transform required errors for field-level validation

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: set default value for User Tokens in manifest.json to improve user experience

Signed-off-by: Deluan <deluan@navidrome.org>

* format

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add margin to outlined input controls for improved spacing

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: remove redundant margin rule for last form control in array items

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: adjust font size of label elements in SchemaConfigEditor for improved readability

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-01-19 20:51:00 -05:00
..

Navidrome Plugin Examples

This folder contains example plugins demonstrating various capabilities and languages supported by Navidrome's plugin system.

Available Examples

Plugin Language Capabilities Description
minimal Go MetadataAgent Basic plugin structure
wikimedia Go MetadataAgent Wikidata/Wikipedia metadata
crypto-ticker Go Scheduler, WebSocket, Cache Real-time crypto prices (demo)
discord-rich-presence Go Scrobbler, Scheduler, WebSocket, Cache, Artwork Discord integration
coverartarchive-py Python MetadataAgent Cover Art Archive
nowplaying-py Python Scheduler, SubsonicAPI Now playing logger
webhook-rs Rust Scrobbler HTTP webhook on scrobble
library-inspector-rs Rust Library, Scheduler Periodic library stats logging
discord-rich-presence-rs Rust Scrobbler, Scheduler, WebSocket, Cache, Artwork Discord integration (Rust)

Building

Prerequisites

  • Go plugins: TinyGo 0.30+
  • Python plugins: extism-py
  • Rust plugins: Rust with wasm32-unknown-unknown target

Build All Plugins

make all

This creates .ndp package files for each plugin.

Build Individual Plugin

make minimal.ndp
make wikimedia.ndp
make discord-rich-presence.ndp

Clean

make clean

Testing Plugins

With Extism CLI

Test any plugin without running Navidrome. First extract the .wasm file from the .ndp package:

# Install: https://extism.org/docs/install

# Extract the wasm file from the package
unzip -p minimal.ndp plugin.wasm > minimal.wasm

# Test a capability function
extism call minimal.wasm nd_get_artist_biography --wasi \
  --input '{"id":"1","name":"The Beatles"}'

For plugins that make HTTP requests, allow the hosts:

unzip -p wikimedia.ndp plugin.wasm > wikimedia.wasm
extism call wikimedia.wasm nd_get_artist_biography --wasi \
  --input '{"id":"1","name":"Yussef Dayes"}' \
  --allow-host "query.wikidata.org" \
  --allow-host "en.wikipedia.org"

With Navidrome

  1. Copy the .ndp file to your plugins folder
  2. Enable plugins in navidrome.toml:
    [Plugins]
    Enabled = true
    Folder = "/path/to/plugins"
    
  3. For metadata agents, add to your agents list:
    Agents = "lastfm,spotify,wikimedia"
    

Creating Your Own Plugin

Option 1: Start from Minimal

Copy the minimal example and modify:

cp -r minimal my-plugin
cd my-plugin
# Edit main.go and manifest.json
tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared .
zip -j my-plugin.ndp manifest.json plugin.wasm

Option 2: Bootstrap with XTP CLI

Generate boilerplate from a schema:

# Install XTP: https://docs.xtp.dylibso.com/docs/cli

xtp plugin init \
  --schema-file ../schemas/metadata_agent.yaml \
  --template go \
  --path ./my-plugin \
  --name my-plugin

# Then create manifest.json and package
cd my-plugin
xtp plugin build
zip -j my-plugin.ndp manifest.json dist/plugin.wasm

Available schemas in ../schemas/:

  • metadata_agent.yaml Artist/album metadata
  • scrobbler.yaml Scrobbling integration
  • lifecycle.yaml Init callbacks
  • scheduler_callback.yaml Scheduled tasks
  • websocket_callback.yaml WebSocket events

Option 3: Different Language

See language-specific examples:

Example Breakdown

Minimal (Go)

The simplest possible plugin. Shows:

  • Manifest export
  • Single capability function
  • Basic input/output handling

Wikimedia (Go)

Real-world metadata agent. Shows:

  • HTTP requests to external APIs
  • SPARQL queries (Wikidata)
  • Error handling
  • Host allowlisting

Discord Rich Presence (Go)

Complex multi-capability plugin. Shows:

  • Scrobbler Receives play events
  • WebSocket Maintains Discord gateway connection
  • Scheduler Heartbeat and timeout management
  • Cache Connection state storage
  • Artwork Getting album art URLs

Cover Art Archive (Python)

Python metadata agent. Shows:

  • extism-py plugin structure
  • HTTP requests
  • JSON handling

Webhook (Rust)

Rust scrobbler. Shows:

  • extism-rs plugin structure
  • HTTP POST requests
  • Minimal dependencies

Resources