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.