Skip to main content

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.
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 its type (or render) function. To patch it:
  1. Find the module that owns the component.
  2. Patch its type or render function with after.
  3. 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.
A React.memo component wraps the real component and stores it on .type. Patch .type, not the memo wrapper.
Patch a memo on .type, not on the memo object. Patching the wrapper has no effect, because React calls the inner component on .type. The wrapper has no render of its own.

Reaching into a render

To patch a component that Metro search can’t find, patch a component you can find, then walk its result 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:
  • type holds the component.
  • props holds its props.
  • props.children holds its rendered children.
Walk that tree to find the element you want.
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. findInTree returns null when Discord’s structure changes, and another plugin may have already changed the tree you’re walking.
  • Add to children instead of replacing it. To insert an element, push onto the existing children array (or make children an 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 on possess) 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:
  1. Call unpatchAll in stop. The component goes back to Discord’s original render, with no leftover wrappers and no double-patching when the plugin is re-enabled.
  2. Force the affected components to re-render.
The second step is needed because 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 mounted screen won’t update on its own. Forcing a re-render brings back the original UI without the user having to navigate away and back. How you force the 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. You must call unpatchAll in stop and then force a re-render. Until something re-renders, the patched output stays on screen even though the patch is gone.
Patch the smallest target that does the job. Wrapping a deep, frequently rendered component runs your code on every render. Find the specific element you need and patch it there.