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.

Follow these best practices to create high-quality plugins that are maintainable, performant, and compatible with other plugins.

Plugin lifecycle

Always clean up in stop()

Every modification your plugin makes should be reversed in the stop() method:
start() {
    BdApi.DOM.addStyle("MyPlugin", ".my-class { color: red; }");
    BdApi.Patcher.after("MyPlugin", SomeModule, "render", this.patchRender);
}

stop() {
    BdApi.DOM.removeStyle("MyPlugin");
    BdApi.Patcher.unpatchAll("MyPlugin");
}

Use bound API instances

Create a plugin-specific API instance for automatic cleanup tracking:
class MyPlugin {
    constructor() {
        // Bound to "MyPlugin"
        this.api = new BdApi("MyPlugin");
    }
    
    start() {
        // Automatically associated with your plugin
        this.api.Patcher.after(SomeModule, "method", callback);
        this.api.Data.save("settings", this.settings);
    }
    
    stop() {
        // One call to unpatch everything
        this.api.Patcher.unpatchAll();
    }
}

Handle plugin reloading

Your plugin should work correctly when reloaded:
start() {
    // Check if already started
    if (this.started) {
        this.api.Logger.warn("Already started, stopping first");
        this.stop();
    }
    
    this.started = true;
    this.loadSettings();
    this.setupPatches();
}

stop() {
    if (!this.started) return;
    
    this.started = false;
    this.cleanup();
}

Error handling

Wrap risky operations in try-catch

Prevent your plugin from crashing Discord:
start() {
    try {
        this.setupPatches();
    } catch (error) {
        this.api.Logger.error("Failed to setup patches:", error);
        BdApi.UI.showToast("MyPlugin failed to start", {type: "error"});
    }
}

Validate module existence

Check that webpack modules exist before using them:
const MessageActions = BdApi.Webpack.getModule(
    BdApi.Webpack.Filters.byKeys("sendMessage")
);

if (!MessageActions) {
    this.api.Logger.error("Failed to find MessageActions module");
    BdApi.UI.showToast("MyPlugin is incompatible with this Discord version", {
        type: "error"
    });
    return;
}

Graceful degradation

Provide fallbacks when features aren’t available:
const MessageModule = BdApi.Webpack.getModule(
    BdApi.Webpack.Filters.byKeys("sendMessage")
);

if (MessageModule) {
    // Full functionality
    this.enableAdvancedFeatures();
} else {
    // Basic functionality
    this.api.Logger.warn("Some features unavailable");
    this.enableBasicFeatures();
}

Performance

Minimize patching

Only patch what you need to modify:
// Patch only the specific function
BdApi.Patcher.after("MyPlugin", MessageComponent, "render", (_, args, ret) => {
    // Minimal modification
    if (ret?.props?.className) {
        ret.props.className += " my-custom-class";
    }
});

Cache webpack modules

Find modules once and reuse them:
class MyPlugin {
    constructor() {
        this.api = new BdApi("MyPlugin");
        this.modules = {};
    }
    
    start() {
        // Cache modules
        this.modules.MessageActions = BdApi.Webpack.getModule(
            BdApi.Webpack.Filters.byKeys("sendMessage")
        );
        
        this.modules.UserStore = BdApi.Webpack.Stores.UserStore;
    }
    
    someMethod() {
        // Reuse cached module
        const user = this.modules.UserStore.getCurrentUser();
    }
}

Debounce frequent operations

Avoid excessive processing:
class MyPlugin {
    constructor() {
        this.api = new BdApi("MyPlugin");
        this.updateDebounced = this.debounce(this.update.bind(this), 300);
    }
    
    debounce(func, wait) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }
    
    onFrequentEvent() {
        // Called many times, but update() only runs once per 300ms
        this.updateDebounced();
    }
    
    update() {
        // Expensive operation
    }
}

Compatibility

Use semantic versioning

Follow semver for version numbers:
  • Major (1.0.0 → 2.0.0): Breaking changes
  • Minor (1.0.0 → 1.1.0): New features, backwards compatible
  • Patch (1.0.0 → 1.0.1): Bug fixes

Check Discord/BD versions

Test compatibility with different versions:
start() {
    const discordVersion = BdApi.version;
    
    if (!this.isCompatible(discordVersion)) {
        BdApi.UI.showNotice(
            `MyPlugin may not work correctly with BetterDiscord ${discordVersion}`,
            {type: "warning", timeout: 0}
        );
    }
}

isCompatible(version) {
    const [major, minor] = version.split(".").map(Number);
    return major >= 1 && minor >= 8;
}

Avoid conflicts with other plugins

Use unique identifiers:
// Prefix classes and IDs
BdApi.DOM.addStyle("MyPlugin", `
    .myplugin-container { /* ... */ }
    #myplugin-settings { /* ... */ }
`);

// Namespace data keys
this.api.Data.save("myplugin:settings", settings);
this.api.Data.save("myplugin:cache", cache);

Code quality

Use consistent naming

Follow JavaScript conventions:
class MyPlugin {
    // PascalCase for classes
    
    // camelCase for methods and variables
    loadSettings() { }
    saveSettings() { }
    
    // UPPER_CASE for constants
    static DEFAULT_TIMEOUT = 5000;
    static API_ENDPOINT = "https://api.example.com";
}

Add JSDoc comments

Document your code:
/**
 * Processes a message and applies transformations
 * @param {Object} message - The message object
 * @param {string} message.content - Message text content
 * @param {string} message.author - Author ID
 * @returns {Object} Transformed message object
 */
processMessage(message) {
    // Implementation
}

Extract magic numbers

Use named constants:
class MyPlugin {
    static TOAST_DURATION = 3000;
    static MAX_RETRIES = 3;
    static DEBOUNCE_DELAY = 300;
    
    start() {
        BdApi.UI.showToast("Started", {
            timeout: MyPlugin.TOAST_DURATION
        });
    }
}

Security

Validate user input

Never trust user input:
updateSetting(key, value) {
    // Validate the key
    const allowedKeys = ["theme", "volume", "enabled"];
    if (!allowedKeys.includes(key)) {
        this.api.Logger.warn(`Invalid setting key: ${key}`);
        return;
    }
    
    // Validate the value
    if (key === "volume" && (value < 0 || value > 100)) {
        this.api.Logger.warn(`Invalid volume: ${value}`);
        return;
    }
    
    this.settings[key] = value;
    this.saveSettings();
}

Sanitize HTML content

Prevent XSS attacks:
const {React} = BdApi;

// Good: React automatically escapes
React.createElement("div", {}, userContent);

// Bad: Direct HTML insertion
document.getElementById("content").innerHTML = userContent;

// If you must use innerHTML, sanitize first
const sanitized = userContent
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
document.getElementById("content").innerHTML = sanitized;

Be careful with eval

Avoid eval() and Function() constructor:
// Never do this
const code = this.api.Data.load("userCode");
eval(code); // DANGEROUS!

// Use safer alternatives
const config = JSON.parse(this.api.Data.load("config"));

Debugging

Use meaningful log messages

Help users troubleshoot:
start() {
    this.api.Logger.info("Starting MyPlugin v1.2.3");
    
    const module = BdApi.Webpack.getModule(filter);
    if (!module) {
        this.api.Logger.error(
            "Failed to find required module.",
            "This may indicate a Discord update.",
            "Please check for plugin updates."
        );
        return;
    }
    
    this.api.Logger.info("Module found, applying patches");
    this.setupPatches();
    this.api.Logger.info("MyPlugin started successfully");
}

Add debug mode

Allow verbose logging:
class MyPlugin {
    start() {
        this.settings = this.api.Data.load("settings") || {};
    }
    
    debug(...args) {
        if (this.settings.debugMode) {
            this.api.Logger.info("[DEBUG]", ...args);
        }
    }
    
    someMethod() {
        this.debug("someMethod called with args:", arguments);
        // Implementation
    }
}

Distribution

Provide clear installation instructions

Include a README with:
  • What the plugin does
  • Installation steps
  • Configuration options
  • Known issues
  • Support contact

Use update URLs

Allow automatic updates:
/**
 * @name MyPlugin
 * @version 1.2.3
 * @updateUrl https://raw.githubusercontent.com/user/repo/main/MyPlugin.plugin.js
 */

Include changelog in updates

Show users what changed:
start() {
    const lastVersion = this.api.Data.load("version");
    const currentVersion = "1.2.3";
    
    if (lastVersion !== currentVersion) {
        this.showChangelog(currentVersion);
        this.api.Data.save("version", currentVersion);
    }
}

showChangelog(version) {
    BdApi.UI.showChangelogModal({
        title: "MyPlugin Updated",
        subtitle: `Version ${version}`,
        changes: [
            {
                title: "New Features",
                type: "added",
                items: [
                    "Added custom themes",
                    "Added keyboard shortcuts"
                ]
            },
            {
                title: "Bug Fixes",
                type: "fixed",
                items: [
                    "Fixed crash on startup",
                    "Fixed settings not saving"
                ]
            }
        ]
    });
}