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.

Patching allows you to modify Discord’s internal functions without rewriting them. BetterDiscord’s Patcher API provides three patch types: before, instead, and after.

Patch types

Before patches

Run your code before the original function and modify its arguments:
BdApi.Patcher.before("MyPlugin", MessageActions, "sendMessage", (thisObject, args) => {
    const [channelId, message] = args;
    
    // Modify the message
    message.content = message.content.toUpperCase();
    
    // args is modified by reference
});
The callback receives:
  • thisObject - The this context of the original function
  • args - Array of arguments (modifiable)

Instead patches

Replace the original function entirely:
BdApi.Patcher.instead("MyPlugin", MessageActions, "sendMessage", (thisObject, args, original) => {
    const [channelId, message] = args;
    
    // Add custom logic
    if (message.content.startsWith("/custom")) {
        // Handle custom command
        return Promise.resolve();
    }
    
    // Call the original function
    return original.apply(thisObject, args);
});
The callback receives:
  • thisObject - The this context
  • args - Array of arguments
  • original - The original function
Always call the original function unless you intentionally want to block it. Not calling original() can break Discord functionality.

After patches

Run your code after the original function and modify its return value:
BdApi.Patcher.after("MyPlugin", MessageComponent.prototype, "render", (thisObject, args, returnValue) => {
    // Modify the React element
    if (returnValue?.props) {
        returnValue.props.className += " my-custom-class";
    }
    
    // Return the modified value
    return returnValue;
});
The callback receives:
  • thisObject - The this context
  • args - Array of arguments
  • returnValue - The return value of the original function

Patching React components

Patching render methods

Patch class component render methods:
const MessageContent = BdApi.Webpack.getByKeys("MessageContent");

BdApi.Patcher.after("MyPlugin", MessageContent.prototype, "render", (thisObject, args, returnValue) => {
    // Access component props
    const { message } = thisObject.props;
    
    // Modify the rendered output
    if (message.content.includes("special")) {
        returnValue.props.style = { backgroundColor: "yellow" };
    }
    
    return returnValue;
});

Patching functional components

Functional components can be patched by targeting their parent object:
const module = BdApi.Webpack.getByKeys("MessageContent");

BdApi.Patcher.after("MyPlugin", module, "MessageContent", (thisObject, args, returnValue) => {
    const [props] = args;
    
    // Modify the returned React element
    if (returnValue?.props) {
        returnValue.props.className += " custom-message";
    }
    
    return returnValue;
});

Injecting React elements

Add your own React elements to Discord’s components:
BdApi.Patcher.after("MyPlugin", MessageHeader.prototype, "render", (thisObject, args, returnValue) => {
    if (!returnValue?.props?.children) return returnValue;
    
    // Create a custom element
    const customElement = BdApi.React.createElement("span", {
        className: "custom-badge",
        style: { color: "red" }
    }, "Custom");
    
    // Inject into children
    if (Array.isArray(returnValue.props.children)) {
        returnValue.props.children.push(customElement);
    }
    
    return returnValue;
});

Managing patches

Unpatch individual patches

Each patch method returns an unpatch function:
const unpatch = BdApi.Patcher.after("MyPlugin", module, "method", callback);

// Later, remove this specific patch
unpatch();

Unpatch all patches

Remove all patches created by your plugin:
BdApi.Patcher.unpatchAll("MyPlugin");
Always unpatch when your plugin is disabled:
module.exports = class MyPlugin {
    start() {
        this.patchMessages();
    }
    
    stop() {
        BdApi.Patcher.unpatchAll("MyPlugin");
    }
};

Get all patches

Retrieve all patches created by your plugin:
const patches = BdApi.Patcher.getPatchesByCaller("MyPlugin");

for (const patch of patches) {
    console.log(patch);
    patch.unpatch(); // Each patch has an unpatch method
}

Advanced techniques

Chaining patches

Multiple plugins can patch the same function. They execute in order:
// Plugin A - runs first
BdApi.Patcher.after("PluginA", module, "method", (thisObject, args, ret) => {
    ret.value = 1;
    return ret;
});

// Plugin B - runs second, receives Plugin A's modifications
BdApi.Patcher.after("PluginB", module, "method", (thisObject, args, ret) => {
    ret.value += 1; // Now ret.value is 2
    return ret;
});

Conditional patching

Apply patches only when conditions are met:
BdApi.Patcher.after("MyPlugin", module, "method", (thisObject, args, returnValue) => {
    // Only modify in specific channels
    if (thisObject.props.channelId !== "123456789") {
        return returnValue;
    }
    
    // Apply modifications
    returnValue.modified = true;
    return returnValue;
});

Patching with error handling

Always handle errors to prevent breaking Discord:
BdApi.Patcher.after("MyPlugin", module, "method", (thisObject, args, returnValue) => {
    try {
        // Your modifications
        returnValue.props.custom = true;
        return returnValue;
    } catch (error) {
        console.error("Patch failed:", error);
        // Return unmodified value on error
        return returnValue;
    }
});

Patching async functions

Handle promises returned by async functions:
BdApi.Patcher.instead("MyPlugin", module, "fetchData", async (thisObject, args, original) => {
    console.log("Fetching data...");
    
    const result = await original.apply(thisObject, args);
    
    console.log("Data received:", result);
    return result;
});
Or modify the promise:
BdApi.Patcher.after("MyPlugin", module, "fetchData", (thisObject, args, returnValue) => {
    if (returnValue instanceof Promise) {
        return returnValue.then(data => {
            // Modify the resolved data
            data.modified = true;
            return data;
        });
    }
    return returnValue;
});

Common pitfalls

Forgetting to return

Always return a value from after patches, even if you don’t modify it. Not returning will cause the function to return undefined.
// Wrong - doesn't return
BdApi.Patcher.after("MyPlugin", module, "method", (thisObject, args, ret) => {
    console.log(ret);
    // Missing return!
});

// Correct
BdApi.Patcher.after("MyPlugin", module, "method", (thisObject, args, ret) => {
    console.log(ret);
    return ret;
});

Modifying frozen objects

Some objects are frozen and can’t be modified. Create new objects instead:
BdApi.Patcher.after("MyPlugin", module, "method", (thisObject, args, returnValue) => {
    // Create a new object instead of modifying
    return {
        ...returnValue,
        custom: true
    };
});

Patching prototypes

When patching class methods, patch the prototype:
// Correct - patches all instances
BdApi.Patcher.after("MyPlugin", Component.prototype, "render", callback);

// Wrong - only patches this specific instance
BdApi.Patcher.after("MyPlugin", componentInstance, "render", callback);

Memory leaks

Always unpatch when your plugin stops:
stop() {
    BdApi.Patcher.unpatchAll("MyPlugin");
    // Also clean up any stored references
    this.cachedData = null;
}

Best practices

  • Use unique caller names to avoid conflicts
  • Always return from after patches
  • Handle errors gracefully
  • Unpatch when stopping your plugin
  • Test patches with other plugins enabled
  • Minimize performance impact in frequently called functions
  • Document which functions you patch
Patching is powerful but can break Discord or conflict with other plugins. Always test thoroughly and handle errors.