Build an Audion plugin.

Reference guide for creating, testing, and distributing extensions. Plain JavaScript, sandboxed execution, and one-click installation.

Prerequisites

Before developing a plugin, make sure you have the basics ready:

  • Audion installed and running (version 1.2.0 or newer).
  • Basic JavaScript knowledge (ES6 syntax, async/await, DOM APIs).
  • A code editor (e.g. Visual Studio Code, Zed, or Neovim).
  • Git for version control and registry publishing.
No build pipeline required. Audion plugins are plain JavaScript. Write your code, provide a manifest, drop the folder into the plugins directory, and Audion loads it directly.

Plugin Manifest: plugin.json

Every plugin begins with a plugin.json manifest file placed at the root of the plugin directory. This file declares identity, entry files, author details, and runtime permission scopes.

Required Manifest Fields

FieldTypeDescription
namestringHuman-readable extension name (e.g. "Last.fm Scrobbler").
versionstringSemantic version string (e.g. "1.0.0").
authorstringDeveloper or organization name.
typestringMust be "js" (or "wasm" for WebAssembly extensions).
entrystringRelative path to entry script (e.g. "index.js").
permissionsstring[]Array of declared security scopes (e.g. ["player:read", "network"]).

Optional Manifest Fields

FieldTypeDescription
descriptionstringOne-line description displayed on extension cards.
categorystringintegration, utility, library, or theme.
repostringPublic GitHub repository URL.
licensestringSPDX open-source license identifier (e.g. "MIT").
min_versionstringMinimum supported Audion build (e.g. "1.2.0").

Sample plugin.json

json
{
  "name": "Play Counter",
  "version": "1.0.0",
  "author": "Audion Contributors",
  "description": "Records playback frequency and total listening time per track",
  "type": "js",
  "entry": "index.js",
  "category": "utility",
  "permissions": [
    "player:read",
    "storage:local"
  ],
  "license": "MIT",
  "repo": "https://github.com/dupitydumb/audion-plugins"
}

Directory Structure

Audion plugins use a simple flat folder structure named with lowercase letters and dashes (kebab-case):

plaintext
your-plugin-name/
├── plugin.json       # Manifest file (required)
├── index.js          # Entry execution script (required)
├── icon.png          # Optional 64x64 icon
└── README.md         # Documentation displayed on marketplace

JavaScript Lifecycle Pattern

Wrap your extension in an Immediately Invoked Function Expression (IIFE) with 'use strict' to avoid leaking variables into the global application context:

javascript
(function () {
    'use strict';

    const PlayCounterPlugin = {
        name: 'Play Counter',
        api: null,

        // Called once when plugin is initialized
        init(api) {
            this.api = api;
            console.log('[PlayCounter] Initialized successfully.');
        },

        // Called when user enables the extension in Settings
        start() {
            this.api.player.on('trackChange', (track) => {
                if (!track) return;
                const current = this.api.storage.get(track.id) || 0;
                this.api.storage.set(track.id, current + 1);
            });
        },

        // Called when user disables the extension
        stop() {
            console.log('[PlayCounter] Stopped.');
        },

        // Called when extension is unloaded or uninstalled
        destroy() {
            this.api = null;
            console.log('[PlayCounter] Cleaned up.');
        }
    };

    // Register on window object
    window.AudionPlugin = PlayCounterPlugin;
})();

Lifecycle Hooks

Audion executes lifecycle methods on your plugin object sequentially as states change:

01

Discovery

Audion scans the local plugins storage directory for valid plugin.json manifest files.

02

Loading

The sandboxed JavaScript runtime compiles and loads the entry script into isolated scope.

03

init(api)

Audion supplies the scoped PluginAPI instance to your extension. Store references and initialize internal state.

04

start()

Fires when the user toggles your plugin on in Settings. Subscribe to track events, scrobble hooks, or player changes.

05

stop()

Fires when the user toggles your plugin off. Pause any polling, background fetch routines, or visualizers.

06

destroy()

Fires when the extension is disabled or removed. Unbind DOM elements, clear interval timers, and flush cached storage.

Resource cleanup in destroy(): Always remove event listeners, unbind interval timers, and clear any injected DOM nodes when destroy() is invoked. Uncleaned intervals lead to memory leaks across long listening sessions.

Available Permissions

Permissions control runtime sandbox capabilities. Request only the scopes required by your extension logic:

ScopeDescriptionAPI Methods Provided
player:readAccess track metadata and playback progress.getCurrentTrack(), isPlaying(), getCurrentTime()
player:controlPlay, pause, skip tracks, and adjust volume.play(), pause(), next(), seek(seconds)
library:readQuery stored music tracks, playlists, and albums.getTracks(), getPlaylists()
storage:localPersist key-value data to dedicated plugin storage.storage.get(k), storage.set(k, v), storage.remove(k)
networkMake outbound HTTP/HTTPS requests to external endpoints.Standard Web fetch() API
ui:injectMount custom user interface panels into player slots.ui.mountSlot(slotName, element)

Storage API

Extensions with storage:local can store configuration and persistent statistics. Storage is sandboxed per plugin, ensuring no namespace collisions:

javascript
// Persist setting
this.api.storage.set('scrobbleEnabled', true);

// Retrieve setting with fallback
const isEnabled = this.api.storage.get('scrobbleEnabled') ?? false;

// Remove key
this.api.storage.remove('scrobbleEnabled');

UI Injection

Extensions with ui:inject can mount visual widgets into designated application drawers or player slots:

javascript
const badge = document.createElement('div');
badge.className = 'audion-plugin-badge';
badge.textContent = 'HQ 96kHz';

// Mount to player drawer
this.api.ui.mountSlot('playerBar:meta', badge);

Testing Locally

Test your extension on your local machine by copying its directory directly into your Audion user data storage:

  • Windows: %APPDATA%\Audion\plugins\your-plugin-name
  • macOS: ~/Library/Application Support/Audion/plugins/your-plugin-name
  • Linux: ~/.config/Audion/plugins/your-plugin-name

Open Audion Settings > Plugins, click Refresh, and toggle your extension on. Press Ctrl+Shift+I (or Cmd+Option+I on macOS) to open the DevTools console and inspect runtime logs.


Publishing to the Registry

The official Audion marketplace automatically indexes community extensions via GitHub repository topics. No pull requests or waiting for review:

  1. Create a public GitHub repository containing your plugin.json manifest at the repository root.
  2. Open your repository Settings on GitHub and add the topic tag: audion-plugins.
  3. The automated indexer scans GitHub hourly for the audion-plugins topic and syncs valid manifests into the marketplace registry.

Deep Link Distribution

Allow users to install your extension directly from your personal website or README using the registered audion:// desktop protocol:

plaintext
audion://install-plugin?url=https://github.com/your-username/your-plugin

Clicking the link prompts Audion to open an installation confirmation dialog, download the release ZIP, and register the sandbox automatically.


Ready to build?

Inspect existing extensions in the marketplace or clone the official template repository to start building your extension immediately.