Prerequisites
Before building a plugin, make sure you have the basics:
- Audion installed and running
- Basic JavaScript knowledge (variables, functions, objects)
- A text editor or IDE (VS Code recommended)
- Git for distributing your plugin (optional)
Plugin Manifestplugin.json
Every plugin begins with a plugin.json manifest. This file tells Audion who made the plugin, what it does, what permissions it needs, and where the code lives.
Required Fields
| Field | Type | Description |
|---|---|---|
name | string | Display name (e.g., "Play Counter") |
version | string | Semantic version (e.g., "1.0.0") |
author | string | Your name or organization |
type | "js" or "wasm" | Plugin type (most should use "js") |
entry | string | Entry file path (e.g., "index.js") |
permissions | string[] | Permissions your plugin needs (see table below) |
Optional Fields
| Field | Type | Description |
|---|---|---|
safe_name | string | Explicit folder name (kebab-case) |
description | string | Short description of your plugin |
repo | string | GitHub repository URL |
manifest_url | string | URL to hosted plugin.json for auto-install |
icon | string | Path to icon file (e.g., "icon.png") |
icon_url | string | URL to hosted icon (overrides icon) |
homepage | string | Plugin website / documentation URL |
category | string | Category: audio | ui | lyrics | library | utility | appearance | social |
tags | string[] | Searchable tags |
license | string | License identifier (e.g., "MIT", "GPL-3.0") |
min_version | string | Minimum Audion version (e.g., "1.0.0") |
ui_slots | string[] | UI injection points (experimental) |
cross_plugin_access | object[] | Cross-plugin communication permissions |
Example Manifest
{
"name": "Play Counter",
"version": "1.0.0",
"author": "Audion Team",
"description": "Tracks how many times each song has been played",
"type": "js",
"entry": "index.js",
"permissions": [
"player:read",
"storage:local",
"ui:inject"
],
"category": "library",
"tags": [
"stats",
"play-count",
"analytics"
],
"license": "MIT"
}Plugin Structure
Audion plugins follow a simple directory structure. The folder name should be lowercase with dashes (kebab-case).
plugin-examples/
└── your-plugin-name/
├── plugin.json # Plugin manifest (required)
├── index.js # Main plugin code — entry point
├── icon.png # Plugin icon (optional)
└── README.md # Documentation (optional)JavaScript Plugin Pattern
Use an Immediately Invoked Function Expression (IIFE) to avoid polluting the global namespace:
(function () {
'use strict';
const YourPlugin = {
name: 'Your Plugin Name',
someState: null,
// Called once when plugin is loaded
init(api) {
this.api = api;
console.log('[YourPlugin] Initialized!');
},
// Called when plugin is enabled
start() {
const track = this.api.player.getCurrentTrack();
if (track) {
console.log('Now playing:', track.title);
}
},
// Called when plugin is disabled
stop() {
console.log('[YourPlugin] Stopped');
},
// Called when plugin is unloaded
destroy() {
// Clean up intervals, listeners, UI elements
console.log('[YourPlugin] Cleaned up');
}
};
// Register globally — required!
window.YourPlugin = YourPlugin;
window.AudionPlugin = YourPlugin;
})();window using both a unique name and window.AudionPlugin. Always use 'use strict'.Lifecycle Hooks
Audion calls specific methods on your plugin object during its lifecycle:
Audion scans the plugins directory for plugin.json files
Runtime loads the entry file
Plugin receives API object. Set up internal state here.
Called when user enables the plugin. Begin functionality.
Called when user disables the plugin
Clean up intervals, event listeners, DOM elements
Always clean up in destroy(). Intervals, event listeners, and DOM elements that are not removed can cause memory leaks.
Available Permissions
Permissions control what your plugin can access. Request only what you need — users review these before enabling.
| Permission | Description | API Methods |
|---|---|---|
player:read | Read playback state and track info | getCurrentTrack(), isPlaying(), getCurrentTime(), getDuration() |
player:control | Control playback | play(), pause(), togglePlay(), next(), prev(), seek() |
library:read | Read music library | getTracks(), getPlaylists() |
library:write | Modify library and playlists | Planned |
storage:local | Store local plugin data | storage.get(), storage.set() |
ui:inject | Inject custom DOM elements | Direct DOM manipulation |
network:fetch | Make HTTP requests | Standard fetch() API |
system:notify | Show system notifications | Browser Notification API |
lyrics:read | Read lyrics data | Planned |
lyrics:write | Modify and save lyrics | Planned |
Example
"permissions": ["player:read", "storage:local"]Storage API
Plugins with the storage:local permission can persist data between app restarts.
// Save data
this.api.storage.set('playCount', { track1: 5, track2: 12 });
// Read data
const counts = this.api.storage.get('playCount');
// → { track1: 5, track2: 12 }
// Delete data
this.api.storage.remove('playCount');Storage is namespaced per plugin — data from "Play Counter" won't conflict with "Theme Customizer".
UI Injection
Plugins with the ui:inject permission can insert custom UI elements into the app. Use it sparingly and clean up in destroy().
// Add a custom button to the player bar
const btn = document.createElement('button');
btn.textContent = 'My Plugin';
btn.onclick = () => alert('Hello from plugin!');
document.querySelector('.player-bar')?.appendChild(btn);
// Store reference for cleanup
this.myButton = btn;// In destroy():
destroy() {
if (this.myButton) {
this.myButton.remove();
this.myButton = null;
}
}UI injection is powerful but dangerous. Always remove injected elements in destroy() and use query-selectors that are resilient to UI changes.
Testing Locally
Once your plugin folder is ready, test it in Audion:
- Copy your plugin folder to the
plugin-examples/directory in Audion (or the plugins directory in your Audion data folder) - Open Audion and go to the Plugins page
- Your plugin should appear in the list. Click Enable to activate it
- Open the Developer Console (
Ctrl+Shift+I) to see yourconsole.logoutput - Reload the plugin after code changes (disable → enable)
Debugging Tips
- Check the browser console for errors
- Verify your
plugin.jsonis valid JSON - Make sure
window.AudionPluginis set - Ensure all referenced files exist in your plugin folder
Publishing to the Registry
The Audion marketplace auto-indexes plugins via GitHub repository topics. No manual PR needed — just push code and add the topic.
Step 1: Create a Public GitHub Repository
- Push your plugin code to a public GitHub repo
- Ensure
plugin.jsonand entry file are at the repository root - Include a
README.mdso users know what the plugin does
Step 2: Add the Topic
Open your repo on GitHub → Settings → scroll toTopics → add audion-plugins.
The audion-plugins registry action scans for repos with the audion-plugins topic. If your repo has valid .json manifest files (starting withplugin.json at the root), it gets indexed automatically.
audion-plugins topic too.Validation Checklist
Before adding the topic, confirm your repo has:
- A valid
plugin.jsonat the root - All referenced files exist (entry point, icons, etc.)
- A
plugin.jsonwith valid JSON syntax and required fields - A public repo (private repos won't be indexed)
Update a Published Plugin
Push changes to your repo. The action re-indexes on the next scan — no topic changes needed.
Distribution via Deep Link
Each plugin on the marketplace has an Install in Audion button that opens the app and prompts the user to install. You can link directly to your plugin from anywhere.
Deep Link Format
audion://install-plugin?url=https://github.com/your-username/your-pluginHow It Works
- User clicks the link — Audion opens (or comes to front)
- A confirmation dialog asks: "Do you want to install the plugin from repository?"
- Audion fetches
plugin.jsonfrom the repo, downloads the entry file and icon - Plugin is installed and ready to enable in the plugin manager
audion://install-plugin?url=https%3A%2F%2Fgithub.com%2Fyour-username%2Fyour-pluginReady to build?
Check out the audion-plugins repo for examples and the marketplace for inspiration.