Skip to main content

Why patch?

Discord’s UI is built from React components you don’t own. To add a button to a menu, hide an element, or wrap a screen in your own, you patch the component: intercept it as it renders and adjust the output. Patching is the core technique behind most visual plugins. You patch with a patcher: an object that wraps a target’s method (including a component’s render) with your own logic, and can revert every patch it made in one call. If you’re new to it, read Function Patching first; this page applies the same patcher to React components.
Everything your plugin shows on screen reaches the UI by patching a component Discord already renders. You return modified elements from inside Discord’s own render, so your UI sits in Discord’s real tree and has access to its context.

The three patch types

A patch hooks a function at one of three points:
Runs after the original and receives its result. Return a new value to replace what it produced, the usual choice for editing a component’s rendered output.

Patching a component’s render

A React component renders through its type (or render) function. Find the module that owns the component, then patch that function with after and return your modified tree.
findByProps and the other findBy* helpers cache their results, so finding the same component a second time is cheap. The searches that stay slow are ones with a manual filter function, which 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

Where you point a patch depends on what kind of component it is. The render function lives in a different place for each.
A React.memo component wraps the real component and stores it on .type. Patch .type, not the memo wrapper itself.
Patch a memo on .type, not on the memo object. Patching the wrapper does nothing, because React calls the inner component on .type, not the wrapper’s own (non-existent) render.

Reaching into a render

Many components are never returned by a Metro search. They are defined inside another component’s module and only exist as elements inside its rendered output. To patch one of these, patch a component you can find, then walk its result to reach the child. The result of a render is a tree of React elements. Each element’s component is on its type, its props are on props, and its rendered children are on props.children. Walk that tree to find the element you want.
Write tree patches to coexist with other plugins. Your plugin won’t be the only one patching a given component, so:
  • Guard every tree lookup with optional chaining and a null check. findInTree returns null when Discord’s structure shifts, and another plugin may have already reshaped the tree you’re walking.
  • Add to children, don’t replace it. When you insert an element, push onto the existing children array (or make children an array if it isn’t one) instead of overwriting it, so an earlier plugin’s additions survive.

Temporary patches from a parent

Sometimes the child you want isn’t an element you can edit in place, but a component whose own render you need to patch, and it has no stable reference Metro can find. The component is created fresh on the parent’s render, so a patch you leave on it would target a reference that is gone on the next render. The pattern: patch the stable parent, and from inside that patch apply a one-shot patch to the child for that render only. Every patch call returns an unpatch function, and possess accepts a { once: true } option that reverts the patch automatically after it fires once.
{ once: true } is what keeps this safe: the child reference is new on every parent render, so the patch must not outlive the render that created it. If you need finer control, capture the unpatch function each patch call returns and call it yourself.

Cleaning up

Every patcher tracks the patches it applied. Call unpatchAll in stop and the component reverts to Discord’s original render: no leftover wrappers, no double-patching when the plugin is re-enabled. Unpatching only restores the original function. Components that already rendered with your patch keep showing the patched output until React renders them again, so a screen that’s currently mounted won’t update on its own. After you unpatch on shutdown, force the affected components to re-render so the original UI comes back without the user navigating away and returning. How you force that re-render depends on the kind of component you patched.
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.
A render patch that survives a disable leaves modified UI on screen with no running plugin behind it. unpatchAll in stop is not optional, and neither is forcing a re-render afterward. Until something re-renders, the old patched output stays on screen even though the patch is gone.
Patch the smallest target that gets the job done. Wrapping a deep, frequently-rendered component runs your code on every render. Find the specific element you need and patch there.