Plugin Development
This page covers the basics of writing a Pipe Bomb plugin. Separate pages cover each registerable component in detail.
Prerequisites
- Node.js (the same version the server runs)
- The Pipe Bomb server SDK (
@pipe-bomb/plugin-sdkor the localsdk/package from the server repo)
Plugin structure
A plugin is a Node.js package. The minimum required files:
my-plugin/
package.json
index.js ← entrypoint (or whatever pipebombEntry/main points to)
package.json
{
"name": "my-plugin",
"version": "1.0.0",
"main": "index.js",
"pipebombEntry": "index.js"
}
nameis the plugin's unique ID. It must match theidfield in any marketplace manifest that lists it.pipebombEntrytakes precedence overmainwhen the server loads the plugin. Use it if your build output is in a non-standard location.
If your plugin has a build script in package.json, the server will run npm run build automatically during installation before moving the plugin into the plugin directory.
Entrypoint
The entrypoint file must export a default class with enable and disable methods:
import type { Plugin, PluginApiContext } from "@pipe-bomb/plugin-sdk";
export default class MyPlugin implements Plugin {
async enable(ctx: PluginApiContext): Promise<void> {
// register capabilities here
}
disable(): void {
// clean up if needed
}
}
enable is called once when the server loads the plugin. disable is currently not called by the server (plugins are removed by deleting their directory and restarting).
PluginApiContext
ctx is your handle to the server. Everything your plugin registers or requests goes through it. Key methods:
| Method | Purpose |
|---|---|
getLogger() |
Logger that prefixes output with your plugin name |
getServerVersion() |
The running server version string |
getPluginPackage() |
Your own package.json metadata |
getPlugin(id) |
Get another loaded plugin's instance |
requestTempDirectory() |
Allocate a temporary working directory |
requestCacheDirectory() |
Allocate a persistent cache directory scoped to your plugin |
getDataClient() |
Read tracks, albums, artists from the database |
requestAuthClient() |
Generate and validate user JWTs |
getPlaylistClient() |
Create and modify playlists |
getWorkflowClient() |
Register workflow triggers and steps |
Registration methods (registerLibraryHandler, registerAttributeSource, etc.) are covered on their respective pages.
Registerable components
| Component | Registration call | Page |
|---|---|---|
| Library Handler | registerLibraryHandler(handler) |
development/Library-Handler |
| Attribute Source | registerAttributeSource(source) |
development/Attribute-Source |
| Track Identifier | registerTrackIdentifier(id) |
users/Identifiers |
| Artist Identifier | registerArtistIdentifier(id) |
users/Identifiers |
| Album Identifier | registerAlbumIdentifier(id) |
users/Identifiers |
| Ephemeral Source | registerEphemeralSource(source) |
users/Ephemeral-Sources |
| Search Source | registerSearchSource(source) |
development/Search-Source |
| Task | registerTask(task) |
See below |
| Global config | registerConfigManager(mgr) |
development/Config-Manager |
| Per-user config | registerUserConfigManager(id, mgr) |
development/Config-Manager |
| Workflow step/trigger | getWorkflowClient() |
development/Workflow-Steps |
| Language strings | registerLanguageDirectory(path) |
— |
| Icons | registerIconDirectory(path) |
— |
| External URL source | registerExternalUrlSource(source) |
— |
Tasks
A task is a background job users can trigger from Settings → Tasks or via a users/Workflows step. Register one with ctx.registerTask(task):
ctx.registerTask({
id: "my-task",
resumable: false,
run: async (ctx) => {
ctx.update(0);
// ... do work ...
ctx.update(1);
},
});
For tasks with sub-variants (e.g., "all" vs "new"), use getSubTasks():
ctx.registerTask({
id: "my-task",
resumable: true,
getSubTasks: () => ["all", "new"] as const,
run: async (ctx, subTaskId) => {
// subTaskId is "all" or "new"
},
});
resumable: true means the server stores progress by run ID so an interrupted task can be detected and restarted at the correct offset.
Related pages
- development/Plugin-System — architecture overview
- development/Library-Handler — implementing a music source
- development/Attribute-Source — implementing metadata enrichment
- development/Search-Source — implementing a search backend
- users/Identifiers — implementing entity identification
- users/Ephemeral-Sources — implementing an external catalog
- development/Config-Manager — plugin settings
- development/Workflow-Steps — custom automation triggers and steps
- users/Marketplace — publishing your plugin