Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/betterdiscord/betterdiscord/llms.txt

Use this file to discover all available pages before exploring further.

BetterDiscord’s addon system allows users to extend Discord with custom plugins and themes. The system is designed to be flexible, safe, and easy to manage.

Architecture

The addon system consists of two main managers that extend a common base class:

AddonManager base class

Both plugins and themes share common functionality through the AddonManager base class:
// Simplified from src/betterdiscord/modules/addonmanager.ts
abstract class AddonManager {
    abstract addonList: Addon[];
    state: Record<string, boolean> = {};
    
    // Common functionality
    initialize() {
        const errors = this.loadAllAddons();
        this.hasInitialized = true;
        return errors;
    }
    
    // File watching
    watchAddons() {
        this.watcher = fs.watch(this.addonFolder, (eventType, filename) => {
            // Hot reload when files change
        });
    }
    
    // State persistence
    loadState() {
        const saved = JsonStore.get(`${this.prefix}s`);
        Object.assign(this.state, saved);
    }
    
    saveState() {
        JsonStore.set(`${this.prefix}s`, this.state);
    }
}
The base class provides:
  • File watching for hot reload
  • State persistence (enabled/disabled status)
  • Common loading and unloading logic
  • Error handling and reporting
  • Metadata parsing from file headers

Plugins

Plugin structure

Plugins are JavaScript files with a specific structure:
/**
 * @name MyPlugin
 * @description Does something cool
 * @version 1.0.0
 * @author YourName
 */

module.exports = class MyPlugin {
    start() {
        console.log("Plugin started!");
    }
    
    stop() {
        console.log("Plugin stopped!");
    }
};

Plugin lifecycle

Plugins go through several stages:
1

Loading

The PluginManager reads the file and parses metadata:
// Metadata extracted from JSDoc comments
const addon = {
    name: "MyPlugin",
    description: "Does something cool",
    version: "1.0.0",
    author: "YourName",
    filename: "MyPlugin.plugin.js",
    // ... other fields
};
2

Initialization

The plugin exports are evaluated and instantiated:
// From src/betterdiscord/modules/pluginmanager.ts
initializeAddon(addon: Plugin) {
    const PluginClass = addon.exports;
    const meta = Object.assign({}, addon);
    delete meta.exports;
    
    // Instantiate or call as function
    const thePlugin = PluginClass.prototype 
        ? new PluginClass(meta) 
        : addon.exports(meta);
    
    addon.instance = thePlugin;
}
3

Starting

When enabled, the plugin’s start() method is called:
startAddon(addon: Plugin) {
    if (!addon.instance?.start) return;
    addon.instance.start();
}
4

Stopping

When disabled, the plugin’s stop() method is called:
stopAddon(addon: Plugin) {
    if (!addon.instance?.stop) return;
    addon.instance.stop();
}

Plugin methods

Plugins can implement several optional methods:
Called when the plugin is enabled.
start() {
    // Initialize your plugin
    this.setup();
}
Called when the plugin is disabled.
stop() {
    // Clean up your plugin
    Patcher.unpatchAll();
}
Called when the plugin is loaded (before start).
load() {
    // One-time setup that persists
    // even when plugin is disabled
}
Returns a settings panel for the plugin.
getSettingsPanel() {
    return MySettingsPanel.render();
}
Called for DOM mutations.
observer(mutation) {
    // React to DOM changes
    if (mutation.addedNodes.length) {
        // Process new nodes
    }
}
Called when Discord switches channels/servers.
onSwitch() {
    // React to navigation
    this.updateUI();
}

File watching and hot reload

The PluginManager watches the plugins folder for changes:
watchAddons() {
    this.watcher = fs.watch(this.addonFolder, async (eventType, filename) => {
        if (!filename.endsWith(this.extension)) return;
        
        // Handle file changes
        if (eventType === "change") {
            // Reload the plugin
            this.reloadAddon(filename);
        }
        else if (eventType === "rename") {
            // File added or removed
            if (fs.existsSync(path.join(this.addonFolder, filename))) {
                this.loadAddon(filename);
            } else {
                this.unloadAddon(filename);
            }
        }
    });
}
When a plugin file is modified, BetterDiscord automatically reloads it if it was enabled, preserving the user’s state.

Themes

Theme structure

Themes are CSS files with metadata in comments:
/**
 * @name MyTheme
 * @description A cool dark theme
 * @version 1.0.0
 * @author YourName
 */

.theme-dark {
    --background-primary: #1a1a1a;
    --background-secondary: #242424;
}

Theme application

When a theme is enabled:
  1. The CSS content is read from the file
  2. It’s injected into the DOM via the DOMManager
  3. A unique ID is assigned for easy removal
enableTheme(theme) {
    DOMManager.injectStyle(
        `bd-theme-${theme.id}`,
        theme.fileContent
    );
}

disableTheme(theme) {
    DOMManager.removeStyle(`bd-theme-${theme.id}`);
}

Theme variables

Themes can use CSS variables to make Discord customizable:
/* Override Discord's variables */
:root {
    --background-primary: #1a1a1a;
    --background-secondary: #242424;
    --text-normal: #e0e0e0;
}

/* Or define your own */
:root {
    --my-accent-color: #7289da;
}

.custom-element {
    color: var(--my-accent-color);
}

Metadata parsing

Both plugins and themes use a common metadata format:
/**
 * @name AddonName
 * @description What it does
 * @version 1.0.0
 * @author AuthorName
 * @authorId 123456789 (Discord user ID)
 * @authorLink https://github.com/username
 * @website https://example.com
 * @source https://github.com/user/repo
 * @updateUrl https://raw.githubusercontent.com/user/repo/main/addon.plugin.js
 * @donate https://paypal.me/username
 * @patreon https://patreon.com/username
 * @invite discordInviteCode
 */
Metadata is parsed from the file header:
const splitRegex = /[^\S\r\n]*?\r?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/;
const metaLine = /^\s*@(\w+)\s+(.*)$/;

// Split comment block into lines
const lines = commentBlock.split(splitRegex);

// Extract metadata
for (const line of lines) {
    const match = line.match(metaLine);
    if (match) {
        const [, key, value] = match;
        metadata[key] = value.trim();
    }
}

Error handling

The addon system has comprehensive error handling:

Loading errors

loadAddon(filename) {
    try {
        // Read and parse file
        const content = fs.readFileSync(filepath, "utf8");
        const addon = this.parseAddon(content, filename);
        
        // Initialize addon
        const error = this.initializeAddon(addon);
        if (error) return error;
        
        // Add to list
        this.addonList.push(addon);
    }
    catch (err) {
        return new AddonError(
            filename,
            filename,
            "Failed to load",
            err,
            this.prefix
        );
    }
}

Error modal

Errors are collected and shown to the user:
// After loading all addons
const errors = {
    plugins: pluginErrors,
    themes: themeErrors
};

if (errors.plugins.length || errors.themes.length) {
    Modals.showAddonErrors(errors);
}

State persistence

Addon states (enabled/disabled) persist across restarts:
// On initialization
loadState() {
    const saved = JsonStore.get("plugins"); // or "themes"
    // saved = { "PluginName": true, "OtherPlugin": false }
    Object.assign(this.state, saved);
}

// When toggling
toggleAddon(id) {
    const newState = !this.state[id];
    this.state[id] = newState;
    this.saveState();
    
    if (newState) this.enableAddon(id);
    else this.disableAddon(id);
}

// Persist to disk
saveState() {
    JsonStore.set("plugins", this.state);
}

Addon interface

The addon data structure:
interface Addon {
    // Metadata
    name: string;
    description: string;
    version: string;
    author: string;
    
    // Optional metadata
    authorId?: string;
    authorLink?: string;
    website?: string;
    source?: string;
    donate?: string;
    patreon?: string;
    invite?: string;
    
    // File information
    filename: string;
    fileContent?: string;
    
    // Internal
    id: string;           // Derived from name
    slug: string;         // URL-safe version of name
    format: string;       // "jsdoc" or other
    size: number;         // File size in bytes
    added: number;        // Timestamp when added
    modified: number;     // Last modified timestamp
    partial?: boolean;    // Failed to load completely
}

Next steps

Creating plugins

Learn how to create your own plugins

Creating themes

Learn how to create your own themes

Builtin features

Explore BetterDiscord’s built-in features