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.

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.

Build docs developers (and LLMs) love