Files
Deluan Quintão 1072e9f7eb chore(plugins): document requiredHosts rules and deprecate pdk.NewHTTPRequest (#6129)
* fix(plugins): align the Python HTTP example with the repo's host-call pattern

Bind http_send with raw memory offsets like nowplaying-py does, drop
guards for fields the host always sends, and document how plugins
without a PDK call host services and which built-in HTTP APIs are
disabled.

* docs(plugins): document the private-address rules for HTTP requiredHosts

Explain in the README and manifest schema that named hosts can't reach
private addresses while IP/CIDR entries and a bare "*" can.

* docs(plugins): document the private-address rules for requiredHosts

Explain in the README and manifest schema that named hosts can't reach
private addresses while IP/CIDR entries and a bare "*" can, for both
HTTP and WebSocket. Inline the single-use HTTP isHostAllowed wrapper.

* feat(plugins): derive Default for Rust host service structs

The ndpgen client.rs template now adds Default to the derive list of host
service structs, as the capability and shared types templates already do.
Plugin authors can now set only the fields they need, for example
HTTPRequest { method, url, ..Default::default() }. The webhook-rs and
discord-rich-presence-rs examples use this form now. The golden files and
the generated nd-pdk-host crate are updated to match.

* feat(plugins): deprecate pdk.NewHTTPRequest in the Go PDK

Navidrome no longer enables extism's http_request host function, so a
request built with pdk.NewHTTPRequest always fails. ndpgen now reads a small
deprecation table and writes a Deprecated: paragraph for the listed extism
functions, in both the WASM wrapper and the native stub. Linters and IDEs
now point plugin authors to host.HTTPSend. The PDK example tests used to
teach NewHTTPRequest. They now use host.HTTPSend and host.HTTPMock.

* docs(plugins): correct requiredHosts rules for websocket and private addresses

Two statements in the plugin docs did not match the code.

The WebSocket section claimed requiredHosts behaves like HTTP. It does not:
host_httpclient.go only consults the allowlist when the list is non-empty and
otherwise falls back to allowing public addresses, while host_websocket.go
always calls isHostInAllowlist, so an absent list blocks every connection.

The HTTP section claimed a named host can never reach a private address.
checkPrivateDial scans the whole requiredHosts list, so a named host does
reach a private address when the same list also holds a covering IP or CIDR.

Reworded both, plus the matching requiredHosts descriptions in
manifest-schema.json, and regenerated manifest_gen.go.
2026-09-12 13:59:46 -04: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)
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-rs.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 ../capabilities/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 ../capabilities/:

  • 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