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:
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
};
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 ;
}
Starting
When enabled, the plugin’s start() method is called: startAddon ( addon : Plugin ) {
if ( ! addon . instance ?. start ) return ;
addon . instance . start ();
}
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
}
getSettingsPanel() - Optional
Returns a settings panel for the plugin. getSettingsPanel () {
return MySettingsPanel . render ();
}
observer(mutation) - Optional
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:
The CSS content is read from the file
It’s injected into the DOM via the DOMManager
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 );
}
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