mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
docs(plugins): sync plugins README with the implementation (#5912)
* docs(plugins): document the Matcher host service The Matcher host service (matcher_matchsongs, permission 'matcher') was missing from the README entirely. Add its section with permission example, the matcher-requires-library rule, result ordering/scoping semantics, and a Go usage example. * docs(plugins): add missing nd_scrobbler_playback_report to Scrobbler The Scrobbler capability has four required methods, but the README said three and omitted nd_scrobbler_playback_report from the function table. Add the table row and document the PlaybackReport input payload and its possible playback states. * docs(plugins): remove stale coverartarchive-as example row The AssemblyScript coverartarchive-as example no longer exists in plugins/examples/ (and no AssemblyScript code remains in the repo), so drop its row from the examples table. * docs(plugins): add Command Line Interface section The 'navidrome plugin' CLI (list, info, validate, enable, disable, edit, rescan) was undocumented; the README's only mention was a single inline 'plugin edit --write-access' reference, and it claimed plugins could only be enabled/disabled via the UI. Add a CLI section with all subcommands and the 'plugin edit' flags, and link it from Runtime Management. * docs(plugins): add pdk and types packages to Go PDK table The Go PDK package table was missing two of the packages that ship under plugins/pdk/go/: 'types' (shared DTOs used across capabilities and host services) and 'pdk' (the extism/go-pdk wrapper the README's own examples already import). * docs(plugins): correct capabilities and host services in examples table Several example rows understated what the plugins implement: the scheduler/websocket callback capabilities were omitted for nowplaying-py, library-inspector-rs, crypto-ticker, and discord-rich-presence-rs, and the Discord example also uses the Config host service. * docs(plugins): state that scheduler/taskqueue permissions require callbacks Manifest validation rejects a plugin that declares the scheduler or taskqueue permission without exporting nd_scheduler_callback or nd_task_execute, but the README worded both callbacks as soft suggestions (TaskWorker was even labeled optional). Make the load-time failure explicit in both capability sections. * docs(plugins): document HTTP, WebSocket, and Task limits and defaults Add the runtime limits plugins actually hit: HTTP default 10s timeout, 5-redirect cap with per-hop host re-validation, and 10MB response cap; the 30s timeout on WebSocket callbacks; and the full task_createqueue parameter list (delayMs, retentionMs) with queue defaults, retention bounds, the 1MB payload cap, and task persistence across restarts. * docs(plugins): include scrobbleRetriever in user-scoped authorization note The ScrobbleRetriever host service added in #5795 is user-scoped like subsonicapi and scrobbler, but the Security section's user-scoped authorization item didn't list it. * docs(plugins): clarify matcher user scoping requires the users permission The Matcher usage example set opts.Username while the section's manifest example declares only matcher+library; without the users permission no users can be assigned to the plugin, so userAccess.resolve rejects the call at runtime. Make the example unscoped and state that user scoping requires the users permission with users assigned. Raised by Codex review on PR #5912.
This commit is contained in:
parent
b0c6d2e444
commit
5c5b849a4e
@ -34,12 +34,14 @@ The plugin system is built on **[Extism](https://extism.org/)**, a cross-languag
|
||||
- [Task](#task)
|
||||
- [WebSocket](#websocket)
|
||||
- [Library](#library)
|
||||
- [Matcher](#matcher)
|
||||
- [Artwork](#artwork)
|
||||
- [SubsonicAPI](#subsonicapi)
|
||||
- [Config](#config)
|
||||
- [Users](#users)
|
||||
- [ScrobbleRetriever](#scrobbleretriever)
|
||||
- [Configuration](#configuration)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
- [Building Plugins](#building-plugins)
|
||||
- [Examples](#examples)
|
||||
- [Security](#security)
|
||||
@ -226,13 +228,14 @@ func ndGetArtistBiography() int32 {
|
||||
|
||||
### Scrobbler
|
||||
|
||||
Integrates with external scrobbling services. All three methods are **required**.
|
||||
Integrates with external scrobbling services. All four methods are **required**.
|
||||
|
||||
| Function | Input | Output | Description |
|
||||
|------------------------------|-----------------------|--------|-----------------------------|
|
||||
| `nd_scrobbler_is_authorized` | `{username}` | `bool` | Check if user is authorized |
|
||||
| `nd_scrobbler_now_playing` | See below | (none) | Send now playing |
|
||||
| `nd_scrobbler_scrobble` | See below | (none) | Submit a scrobble |
|
||||
| Function | Input | Output | Description |
|
||||
|---------------------------------|--------------|--------|------------------------------|
|
||||
| `nd_scrobbler_is_authorized` | `{username}` | `bool` | Check if user is authorized |
|
||||
| `nd_scrobbler_now_playing` | See below | (none) | Send now playing |
|
||||
| `nd_scrobbler_scrobble` | See below | (none) | Submit a scrobble |
|
||||
| `nd_scrobbler_playback_report` | See below | (none) | Send playback state report |
|
||||
|
||||
> **Important:** Scrobbler plugins require the `users` permission in their manifest. Scrobble events are only sent for users assigned to the plugin through Navidrome's configuration.
|
||||
|
||||
@ -270,6 +273,25 @@ Integrates with external scrobbling services. All three methods are **required**
|
||||
}
|
||||
```
|
||||
|
||||
**PlaybackReport Input:**
|
||||
|
||||
Same `username` and `track` fields, plus playback state details:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "john",
|
||||
"track": { ... },
|
||||
"state": "playing",
|
||||
"positionMs": 45000,
|
||||
"playbackRate": 1.0,
|
||||
"playerId": "player-id",
|
||||
"playerName": "My Client",
|
||||
"timestamp": 1703270400
|
||||
}
|
||||
```
|
||||
|
||||
`state` is one of `starting`, `playing`, `paused`, `stopped`, or `expired`.
|
||||
|
||||
**Error Handling:**
|
||||
|
||||
On success, return `0`. On failure, use `pdk.SetError()` with one of these error types:
|
||||
@ -309,7 +331,7 @@ Each match contains a `song` reference and a `similarity` score (float64, 0.0–
|
||||
|
||||
### TaskWorker
|
||||
|
||||
Processes tasks from a queue. The method is **optional** — export it if your plugin uses the [Task](#task) host service for background work.
|
||||
Processes tasks from a queue. **Required** if your plugin uses the [Task](#task) host service: declaring the `taskqueue` permission without exporting this function fails the plugin load.
|
||||
|
||||
| Function | Input | Output | Description |
|
||||
|---------------------|---------------------------------------------|---------|----------------------|
|
||||
@ -329,7 +351,7 @@ Useful for initializing connections, scheduling recurring tasks, etc. Errors are
|
||||
|
||||
### SchedulerCallback
|
||||
|
||||
Receives scheduled task events. **Required** if your plugin uses the [Scheduler](#scheduler) host service.
|
||||
Receives scheduled task events. **Required** if your plugin uses the [Scheduler](#scheduler) host service: declaring the `scheduler` permission without exporting this function fails the plugin load.
|
||||
|
||||
| Function | Input | Output | Description |
|
||||
|---------------------------|----------------------------------------------|--------|-----------------------------|
|
||||
@ -346,6 +368,8 @@ Receives WebSocket events. Export any subset of these to handle events from the
|
||||
| `nd_websocket_on_error` | `{connectionId, error}` | Connection error |
|
||||
| `nd_websocket_on_close` | `{connectionId, code, reason}` | Connection closed |
|
||||
|
||||
Each callback invocation is subject to a 30-second timeout.
|
||||
|
||||
---
|
||||
|
||||
## Host Services
|
||||
@ -390,6 +414,8 @@ Make HTTP requests to external services. This is a dedicated host service (separ
|
||||
|-------------|----------------------------------------------------------|----------------------------------|
|
||||
| `http_send` | `method, url, headers, body, timeoutMs, noFollowRedirects` | `statusCode, headers, body` |
|
||||
|
||||
**Limits:** Requests time out after 10 seconds by default (override per request with `timeoutMs`). Redirects are followed up to 5 times, re-checking the allowed hosts on every hop. Response bodies are capped at 10MB.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```go
|
||||
@ -615,13 +641,21 @@ Background task queue with retry support. Plugins enqueue tasks and process them
|
||||
|
||||
**Host functions:**
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|---------------------|---------------------------------------------------|----------------------------|
|
||||
| `task_createqueue` | `name, concurrency, maxRetries, backoffMs, ...` | Create a named task queue |
|
||||
| `task_enqueue` | `queueName, payload` | Add a task to the queue |
|
||||
| `task_get` | `taskID` | Get task status and result |
|
||||
| `task_cancel` | `taskID` | Cancel a pending task |
|
||||
| `task_clearqueue` | `queueName` | Remove all tasks from queue|
|
||||
| Function | Parameters | Description |
|
||||
|---------------------|---------------------------------------------------------------|----------------------------|
|
||||
| `task_createqueue` | `name, concurrency, maxRetries, backoffMs, delayMs, retentionMs` | Create a named task queue |
|
||||
| `task_enqueue` | `queueName, payload` | Add a task to the queue |
|
||||
| `task_get` | `taskID` | Get task status and result |
|
||||
| `task_cancel` | `taskID` | Cancel a pending task |
|
||||
| `task_clearqueue` | `queueName` | Remove all tasks from queue|
|
||||
|
||||
Tasks are persisted to SQLite, so pending tasks survive server restarts. Queue behavior:
|
||||
|
||||
- `concurrency` – Parallel workers (default 1), capped by the manifest's `maxConcurrency`
|
||||
- `maxRetries` – Retries for a failed task (default 0); `backoffMs` (default 1000) doubles on each retry
|
||||
- `delayMs` – Minimum delay between consecutive task starts, useful for rate limiting (default 0)
|
||||
- `retentionMs` – How long finished tasks are kept (default 1 hour, min 1 minute, max 1 week)
|
||||
- Payloads are capped at 1MB
|
||||
|
||||
**Usage:**
|
||||
|
||||
@ -747,6 +781,45 @@ for _, lib := range libraries {
|
||||
}
|
||||
```
|
||||
|
||||
### Matcher
|
||||
|
||||
Match externally-obtained songs (e.g. results from a recommendation or similarity API) to tracks in the local library, reusing Navidrome's matching algorithm (ID > MBID > ISRC > fuzzy title).
|
||||
|
||||
**Manifest permission:**
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": {
|
||||
"matcher": {
|
||||
"reason": "Resolve external recommendations to library tracks"
|
||||
},
|
||||
"library": {
|
||||
"reason": "Required by the matcher permission"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Important:** The `matcher` permission requires the `library` permission.
|
||||
|
||||
**Host functions:**
|
||||
|
||||
| Function | Parameters | Returns |
|
||||
|----------------------|---------------|-------------------------|
|
||||
| `matcher_matchsongs` | `songs, opts` | Array of matched tracks |
|
||||
|
||||
The result has one entry per input song, in the same order; the entry for a song with no match is empty. Results are limited to the libraries the plugin (and the scoped user, if any) can access. Set `opts.username` to run the match as a specific user: their favorites and ratings inform tiebreaking, and the returned tracks carry their annotations. User scoping additionally requires the [`users`](#users) permission, with users assigned to the plugin.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```go
|
||||
import "github.com/navidrome/navidrome/plugins/pdk/go/types"
|
||||
|
||||
matches, err := host.MatcherMatchSongs([]types.SongRef{
|
||||
{Name: "Song Title", Artists: []types.ArtistRef{{Name: "Artist Name"}}},
|
||||
}, host.MatchOptions{})
|
||||
```
|
||||
|
||||
### Artwork
|
||||
|
||||
Generate public URLs for Navidrome artwork (albums, artists, tracks, playlists).
|
||||
@ -1008,6 +1081,29 @@ For more advanced access (listing keys, integer values), use the [Config](#confi
|
||||
|
||||
---
|
||||
|
||||
## Command Line Interface
|
||||
|
||||
Manage plugins from the command line with `navidrome plugin`:
|
||||
|
||||
| Command | Description |
|
||||
|--------------------------------------------------------|------------------------------------------------------------|
|
||||
| `navidrome plugin list [-f table\|csv\|json]` | List installed plugins |
|
||||
| `navidrome plugin info <id\|file.ndp> [-f text\|json]` | Show details for an installed plugin or a `.ndp` package |
|
||||
| `navidrome plugin validate <id\|file.ndp>` | Validate an installed plugin or a `.ndp` package manifest |
|
||||
| `navidrome plugin enable <id>` | Enable a plugin |
|
||||
| `navidrome plugin disable <id>` | Disable a plugin |
|
||||
| `navidrome plugin edit <id>` | Update a plugin's config and/or permissions |
|
||||
| `navidrome plugin rescan` | Re-discover plugins in the plugins folder |
|
||||
|
||||
**`plugin edit` flags:**
|
||||
|
||||
- `--config <json>` / `--config-file <path>` – Set the plugin configuration (`-` reads from stdin)
|
||||
- `--users <list>` / `--all-users` – Usernames the plugin may access (comma-separated or JSON array), or all users
|
||||
- `--libraries <list>` / `--all-libraries` – Library IDs the plugin may access (comma-separated or JSON array), or all libraries
|
||||
- `--write-access` / `--no-write-access` – Allow or deny the plugin write access to libraries
|
||||
|
||||
---
|
||||
|
||||
## Building Plugins
|
||||
|
||||
### Supported Languages
|
||||
@ -1070,6 +1166,8 @@ replace github.com/navidrome/navidrome => ../../..
|
||||
| `scheduler` | `plugins/pdk/go/scheduler` | Scheduled task callbacks |
|
||||
| `websocket` | `plugins/pdk/go/websocket` | WebSocket event handlers |
|
||||
| `host` | `plugins/pdk/go/host` | Host service SDK (all services) |
|
||||
| `types` | `plugins/pdk/go/types` | Shared data types (tracks, artists, song refs) |
|
||||
| `pdk` | `plugins/pdk/go/pdk` | Low-level helpers (wraps extism/go-pdk: config, logging, memory) |
|
||||
|
||||
See the example plugins in [examples/](examples/) for complete usage patterns.
|
||||
|
||||
@ -1168,12 +1266,11 @@ See [examples/](examples/) for complete working plugins:
|
||||
| [minimal](examples/minimal/) | Go | MetadataAgent | – | Basic structure example |
|
||||
| [wikimedia](examples/wikimedia/) | Go | MetadataAgent | HTTP | Wikidata/Wikipedia integration |
|
||||
| [coverartarchive-py](examples/coverartarchive-py/) | Python | MetadataAgent | HTTP | Cover Art Archive |
|
||||
| [coverartarchive-as](examples/coverartarchive-as/) | AssemblyScript | MetadataAgent | HTTP | Cover Art Archive |
|
||||
| [webhook-rs](examples/webhook-rs/) | Rust | Scrobbler | HTTP | HTTP webhooks |
|
||||
| [nowplaying-py](examples/nowplaying-py/) | Python | Lifecycle | Scheduler, SubsonicAPI | Periodic now-playing logger |
|
||||
| [library-inspector-rs](examples/library-inspector-rs/) | Rust | Lifecycle | Library, Scheduler | Periodic library stats logging |
|
||||
| [crypto-ticker](examples/crypto-ticker/) | Go | Lifecycle | WebSocket, Scheduler | Real-time crypto prices demo |
|
||||
| [discord-rich-presence-rs](examples/discord-rich-presence-rs/) | Rust | Scrobbler | HTTP, WebSocket, Cache, Scheduler, Artwork | Discord integration |
|
||||
| [webhook-rs](examples/webhook-rs/) | Rust | Scrobbler | HTTP | HTTP webhooks |
|
||||
| [nowplaying-py](examples/nowplaying-py/) | Python | Lifecycle, SchedulerCallback | Scheduler, SubsonicAPI | Periodic now-playing logger |
|
||||
| [library-inspector-rs](examples/library-inspector-rs/) | Rust | Lifecycle, SchedulerCallback | Library, Scheduler | Periodic library stats logging |
|
||||
| [crypto-ticker](examples/crypto-ticker/) | Go | Lifecycle, SchedulerCallback, WebSocketCallback | WebSocket, Scheduler | Real-time crypto prices demo |
|
||||
| [discord-rich-presence-rs](examples/discord-rich-presence-rs/) | Rust | Scrobbler, SchedulerCallback, WebSocketCallback | HTTP, WebSocket, Cache, Scheduler, Artwork, Config | Discord integration |
|
||||
|
||||
---
|
||||
|
||||
@ -1186,7 +1283,7 @@ Plugins run in a secure WebAssembly sandbox provided by [Extism](https://extism.
|
||||
3. **No Network Listeners** – Plugins cannot bind ports
|
||||
4. **Config Isolation** – Plugins only receive their own config section
|
||||
5. **Memory Limits** – Controlled by the WebAssembly runtime
|
||||
6. **User-Scoped Authorization** – Plugins with `subsonicapi` or `scrobbler` capabilities can only access/receive events for users assigned to them through Navidrome's configuration
|
||||
6. **User-Scoped Authorization** – Plugins with `subsonicapi`, `scrobbleRetriever`, or `scrobbler` capabilities can only access/receive events for users assigned to them through Navidrome's configuration
|
||||
7. **Users Permission** – Plugins requesting user access must be explicitly configured with allowed users; sensitive data (passwords, emails) is never exposed
|
||||
|
||||
---
|
||||
@ -1201,7 +1298,7 @@ If `AutoReload` is disabled, Navidrome needs to be restarted to pick up plugin c
|
||||
|
||||
### Enabling/Disabling Plugins
|
||||
|
||||
Plugins can be enabled/disabled via the Navidrome UI. The plugin state is persisted in the database.
|
||||
Plugins can be enabled/disabled via the Navidrome UI or the [`navidrome plugin` CLI](#command-line-interface). The plugin state is persisted in the database.
|
||||
|
||||
### Important Notes
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user