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.
Discord’s UI is built with React. BetterDiscord provides utilities for working with React components and the React tree.
Accessing React
React is available through BdApi:
const React = BdApi.React;
// Create elements
const element = React.createElement("div", { className: "my-element" }, "Hello World");
// Use JSX if your build process supports it
const element = <div className="my-element">Hello World</div>;
ReactUtils
The BdApi.ReactUtils namespace provides utilities for working with React internals.
Getting internal instances
Get the React fiber node from a DOM element:
const element = document.querySelector(".message-content");
const fiber = BdApi.ReactUtils.getInternalInstance(element);
if (fiber) {
console.log("Component:", fiber.type);
console.log("Props:", fiber.memoizedProps);
console.log("State:", fiber.memoizedState);
}
Getting owner instances
Find the nearest React component instance (usually a class component):
const element = document.querySelector(".message-content");
const owner = BdApi.ReactUtils.getOwnerInstance(element);
if (owner) {
console.log("Component instance:", owner);
console.log("Props:", owner.props);
console.log("State:", owner.state);
}
Filter by component name:
// Only find components with these names
const owner = BdApi.ReactUtils.getOwnerInstance(element, {
include: ["Message", "MessageContent"]
});
// Find components excluding these names
const owner = BdApi.ReactUtils.getOwnerInstance(element, {
exclude: ["Popout", "Tooltip"]
});
Default exclusions: ["Popout", "Tooltip", "Scroller", "BackgroundFlash"]
Use a custom filter:
const owner = BdApi.ReactUtils.getOwnerInstance(element, {
filter: (instance) => {
return instance.props.message?.author?.id === "123456789";
}
});
Wrapping HTML elements
Wrap HTML elements in a React component:
const htmlElement = document.createElement("div");
htmlElement.textContent = "Custom content";
const WrappedComponent = BdApi.ReactUtils.wrapElement(htmlElement);
// Use in React
BdApi.showConfirmationModal("Title", [
BdApi.React.createElement(WrappedComponent)
]);
Wrap multiple elements:
const elements = [
document.createElement("div"),
document.createElement("span")
];
const WrappedComponent = BdApi.ReactUtils.wrapElement(elements);
Working with exotic components
Unwrap components from React.memo, React.forwardRef, and React.lazy:
const wrappedComponent = React.memo(MyComponent);
const actualComponent = BdApi.ReactUtils.getType(wrappedComponent);
console.log(actualComponent === MyComponent); // true
This is useful when patching:
const module = BdApi.Webpack.getByKeys("MessageContent");
const Component = BdApi.ReactUtils.getType(module.MessageContent);
// Now you can patch the actual component
BdApi.Patcher.after("MyPlugin", module, "MessageContent", callback);
Rendering functional components
Render functional components outside of React’s normal lifecycle:
const FunctionalComponent = (props) => {
const [count, setCount] = React.useState(0);
return React.createElement("div", null, `Count: ${count}`);
};
// Wrap to render with hooks support
const wrapped = BdApi.ReactUtils.wrapInHooks(FunctionalComponent);
const result = wrapped({ someProp: "value" });
console.log(result); // Rendered React element
Provide custom hook implementations:
const wrapped = BdApi.ReactUtils.wrapInHooks(FunctionalComponent, {
useState(initial) {
return ["custom value", () => {}];
},
useEffect() {
console.log("Effect called");
}
});
wrapInHooks uses mock hooks. State changes won’t cause re-renders. Use this only for inspecting components or testing renders.
Creating React components
Functional components
Create simple functional components:
function MyComponent(props) {
return BdApi.React.createElement("div", {
className: "my-component",
style: { color: props.color }
}, props.children);
}
With JSX:
function MyComponent({ color, children }) {
return (
<div className="my-component" style={{ color }}>
{children}
</div>
);
}
Using hooks
Use React hooks in your components:
function Counter() {
const [count, setCount] = BdApi.React.useState(0);
BdApi.React.useEffect(() => {
console.log("Count changed:", count);
}, [count]);
return BdApi.React.createElement("button", {
onClick: () => setCount(count + 1)
}, `Count: ${count}`);
}
Common hooks:
useState - Component state
useEffect - Side effects
useCallback - Memoized callbacks
useMemo - Memoized values
useRef - Persistent references
useContext - Context values
Class components
Create class-based components:
class MyComponent extends BdApi.React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log("Component mounted");
}
componentWillUnmount() {
console.log("Component will unmount");
}
render() {
return BdApi.React.createElement("div", null,
`Count: ${this.state.count}`
);
}
}
Modifying React trees
Finding elements
Search through React elements:
function findInTree(tree, filter) {
if (filter(tree)) return tree;
if (tree?.props?.children) {
if (Array.isArray(tree.props.children)) {
for (const child of tree.props.children) {
const result = findInTree(child, filter);
if (result) return result;
}
} else {
return findInTree(tree.props.children, filter);
}
}
return null;
}
// Usage in a patch
BdApi.Patcher.after("MyPlugin", module, "render", (thisObject, args, returnValue) => {
const button = findInTree(returnValue,
e => e?.props?.onClick && e.type === "button"
);
if (button) {
button.props.className += " custom-button";
}
return returnValue;
});
Modifying children
Safely modify React children:
BdApi.Patcher.after("MyPlugin", module, "render", (thisObject, args, returnValue) => {
if (!returnValue?.props?.children) return returnValue;
const children = returnValue.props.children;
// Handle arrays
if (Array.isArray(children)) {
children.push(BdApi.React.createElement("span", null, "Added!"));
}
// Handle single child
else {
returnValue.props.children = [
children,
BdApi.React.createElement("span", null, "Added!")
];
}
return returnValue;
});
Using React.Children
Utilities for working with children:
function MyComponent({ children }) {
const childArray = BdApi.React.Children.toArray(children);
// Map over children
const modifiedChildren = BdApi.React.Children.map(children, child => {
if (BdApi.React.isValidElement(child)) {
return BdApi.React.cloneElement(child, {
className: "modified"
});
}
return child;
});
return BdApi.React.createElement("div", null, modifiedChildren);
}
Context
Reading context
Access React context values:
const ThemeContext = BdApi.Webpack.getByKeys("ThemeContext")?.ThemeContext;
function ThemedComponent() {
const theme = BdApi.React.useContext(ThemeContext);
return BdApi.React.createElement("div", {
style: { backgroundColor: theme === "dark" ? "#000" : "#fff" }
});
}
Creating context
Create your own context:
const MyContext = BdApi.React.createContext("default value");
function Provider({ children }) {
return BdApi.React.createElement(MyContext.Provider, {
value: "provided value"
}, children);
}
function Consumer() {
const value = BdApi.React.useContext(MyContext);
return BdApi.React.createElement("div", null, value);
}
Node patching
Create a node patcher for advanced DOM manipulation:
const nodePatcher = BdApi.ReactUtils.createNodePatcher();
nodePatcher.patch({
callback: (node) => {
if (node.classList.contains("message-content")) {
node.style.backgroundColor = "yellow";
}
}
});
// Clean up when done
nodePatcher.unpatch();
Best practices
- Always clean up in
componentWillUnmount or useEffect cleanup
- Use
React.memo for expensive components
- Avoid creating components inside render methods
- Handle missing props and children gracefully
- Use proper dependency arrays in
useEffect and useMemo
- Test your components with React DevTools
Modifying React’s internal fiber structure can cause crashes or unexpected behavior. Always test thoroughly.