What is patching?
Patching (also called monkey-patching) wraps an existing function with your own code, which runs before it, after it, or in place of it. Plugins use it to change behavior they don’t own, from a Discord method inside a module to a component’s render. Doing this by hand means keeping a reference to the original, restoring it later, and working alongside other plugins that patch the same function. Thepatcher handles all three. Unbound re-exports it unchanged from the possess package, the same patcher its own built-ins use.
Creating a patcher
CallcreatePatcher with your plugin’s id to get a patcher instance. Every patch you apply through it is grouped under that name, so one unpatchAll call in stop reverts all of them.
string | PatchOptions
required
A caller name, or a full
PatchOptions object. A string is shorthand for { caller: name }. If you pass an object without caller, a random UUID is generated for it.typeof before
Adds a
before patch. Your defaultOptions are merged with the options you pass per call, and per-call options win.typeof after
Adds an
after patch, with the same option merging.typeof instead
Adds an
instead patch, with the same option merging.() => void
Removes every patch registered under this instance’s
caller. Equivalent to patcher.unpatchAllByCaller(caller).Two instances created with the same name share a caller, so calling
unpatchAll on either one removes the patches of both.The three patch types
Pick the type by when your code needs to run relative to the original:- Before
- After
- Instead
Runs before the original. Mutate
args, or return a new array, to change what the original receives. Use it to rewrite inputs.Applying a patch
before, after, and instead all take the same arguments and return a function that removes that one patch. You can call them on an instance or directly on patcher. Patches applied directly on patcher have no caller unless you pass one in options.
PatchParent
required
The object (or function) that owns the method.
PropOf<parent>
required
The property name of the method on
parent. The type only accepts keys whose values are functions or constructors.PatchCallback
required
Called with a
PatchContext each time the patched method runs. It is also called with the same this as the patched method.parent[method] with a wrapper function. Later patches on the same method reuse that wrapper. The wrapper copies the original’s own properties (except length), and its toString() returns the original’s source.
The patch context
Every callback receives a context object. All callbacks on the same call share one context, so abefore patch can store data on it for an after patch to read.
Args
The arguments the method was called with. Mutate it, or return a new array from a
before patch, to change what the original receives.Self
The
this value the method was called with.Res | null
The return value so far. It is
null until the original or an instead patch produces a value, so read it in after patches.(...args) => Res
Calls the unpatched method. It always uses the
this of the current call, so original(...args) keeps the right context. When the patched function is called with new, original constructs with new too.Calling
original from a before or after patch runs the original an extra time. The wrapper still makes its own call.The this context
ctx.this is the this value JavaScript resolved at the call site. Its value depends on how the function was called.
- Class Method
- Object Method
- Bound Function
For a method on a class prototype,
this is the instance the method was called on. Read its props, state, or fields through ctx.this.Return values
A callback can change the call by mutatingctx or by returning a value. The wrapper treats the return value differently for each patch type:
Returning
undefined from instead or after leaves ctx.result as it was. To make the call return undefined, set ctx.result = undefined instead of returning it.
Execution order
On every call, the wrapper:- Builds the context, with
resultset tonull. - Runs each
beforepatch in registration order. - If there are no
insteadpatches, calls the original withctx.argsand stores its return value inctx.result. Otherwise it runs eachinsteadpatch in registration order and does not call the original. - Runs each
afterpatch in registration order. Each one sees the result so far. - Returns
ctx.result.
Typing a patch
The patcher infersArgs, Res, and Self from the target function. If the module is typed, args, result, and this are typed too, with no annotations needed.
PatchContext<Args, Result, Self>. All three type parameters are optional and default to any, so pass only the ones you need.
Patch options
PatchOptions can be passed to createPatcher as defaults or to a single patch call as the fourth argument.
string
The name the patch is grouped under.
unpatchAllByCaller and an instance’s unpatchAll remove patches by this name.boolean
Removes the patch after its callback runs once. Use it for targets that only exist for one call or one render, such as a child component created fresh on each parent render. See Temporary patches from a parent.
A
once patch is removed only after its callback returns. If the callback throws, the patch stays in place and runs again on the next call.Patching classes
The same three methods work on constructors. Patch the class by name on its parent module.args holds the constructor arguments, and in an after patch result is the new instance.
Errors in callbacks
Each callback runs inside its owntry/catch. When one throws, the wrapper logs the error with console.error and moves on to the next patch. Other patches and the call itself still run, so one broken plugin doesn’t stop other plugins’ patches on the same function.
The log line names the patch type:
Cleaning up
CallunpatchAll on your instance in your plugin’s stop. Each function goes back to its original, so the next plugin, or your plugin when re-enabled, can patch it cleanly.
When the last patch on a method is removed, the original function is written back to
parent[method] and the method’s entry is dropped from patches.
Inspecting patches
Use these exports to check what is patched, for debugging and tooling.PatchedModule[]
One entry for every patched method. Each entry has
parent (a weak reference, read it with parent.deref()), method, original, and patches: a record of Sets keyed by PatchType.enum
Before = 0, Instead = 1, After = 2. These are the keys of PatchedModule.patches.unique symbol
Symbol.for('$$patched$$'). Every wrapper function has this property set to true.The patcher keeps only a weak reference to each parent, so patching an object doesn’t keep it in memory. Where
WeakRef isn’t available, it falls back to a strong reference.