Why patch?
To change Discord’s UI, you patch the component that renders it: intercept the component as it renders and adjust the output. Use this to add a button to a menu, hide an element, or wrap a screen in your own. Discord’s UI is made of React components your plugin doesn’t own, so most visual plugins work this way. Patches are applied with a patcher. A patcher wraps a target’s method, including a component’s render, with your own code, and can revert all of its patches in one call. If you haven’t used it before, read Patcher first. This page applies the same patcher to React components.Everything your plugin shows on screen gets there by patching a component Discord already renders. You return modified elements from inside Discord’s own render, so your UI is part of Discord’s real tree and can access its context.
The three patch types
A patch hooks a function at one of three points.- After
- Before
- Instead
Runs after the original and receives its
result. Return a new value to replace the original’s output. This is the usual choice for editing what a component renders.Patching a component’s render
A React component renders through itstype (or render) function. To patch it:
- Find the module that owns the component.
- Patch its
typeorrenderfunction withafter. - Return your modified tree from the callback.
findByProps and the other findBy* helpers cache their results, so a repeat lookup of the same component is cheap. Searches with a manual filter function stay slow, because they can’t be cached by default. If you need one, wrap it with createCacheable so its result is remembered after the first cold start.Component kinds
Each kind of component keeps its render function in a different place, so where you patch depends on the kind.- Memo
- Class
- Function
A
React.memo component wraps the real component and stores it on .type. Patch .type, not the memo wrapper.Reaching into a render
To patch a component that Metro search can’t find, patch a component you can find, then walk itsresult to reach the child. Many components are like this: they are defined inside another component’s module and only exist as elements in its rendered output.
A render result is a tree of React elements. On each element:
typeholds the component.propsholds its props.props.childrenholds its rendered children.
Other plugins may patch the same component, so write tree patches that work alongside them:
- Guard every tree lookup with optional chaining and a null check.
findInTreereturnsnullwhen Discord’s structure changes, and another plugin may have already changed the tree you’re walking. - Add to
childreninstead of replacing it. To insert an element, push onto the existing children array (or makechildrenan array if it isn’t one). Overwriting it would remove an earlier plugin’s additions.
Temporary patches from a parent
To patch a child component’s own render when Metro can’t find a stable reference to it, patch the stable parent. Inside that patch, apply a one-time patch to the child that only lasts for that render. This is needed when the child is created fresh on each parent render. A patch left on it would target a reference that no longer exists on the next render. Every patch call returns an unpatch function, and the patcher (built onpossess) accepts a { once: true } option that reverts the patch automatically after it fires once.
{ once: true } is what makes this safe. The child reference is new on every parent render, so the patch must not outlive the render that created it. For finer control, keep the unpatch function the patch call returns and call it yourself.Cleaning up
Cleaning up takes two steps:- Call
unpatchAllinstop. The component goes back to Discord’s original render, with no leftover wrappers and no double-patching when the plugin is re-enabled. - Force the affected components to re-render.
- Class
- Function
A class component instance has a built-in
forceUpdate method. If your patch captured the instance through this, call self.forceUpdate() to re-render it.