A hook runs your code before or after a game function.
You can read arguments, change arguments, skip the original, or change the return value.
geode.hook registers a callback and returns a handle.
Types match the generated stub types/geode.d.luau.
geode.hook(target: string, callback: HookCallbackTable) -> HookHandleRegisters a callback on a target function. You must pass exactly two arguments. An unknown target raises an error. The hook is enabled when you register it.
See Examples.
The target id is namespace.Class:method/argCount.
namespace.Classis the bound class, for examplegeode.gd.GameManager.methodis the method name.argCountis the argument count, not countingself.
Examples: geode.gd.MenuLayer:init/0, geode.gd.GameManager:setIntGameVariable/2.
The id uses argument count only, not argument types. C++ overloads that share the same arity hook the same target, codegen rejects ambiguous same-arity overloads at build time.
type HookCallbackTable = {
before: ((...any) -> any?)?,
after: ((...any) -> any?)?,
priority: number?,
}Provide at least one of before or after. priority orders callbacks on the same target, default 0.
Normal API calls reject nil object pointers.
Hook before and after callbacks may pass nil for object pointer arguments when you need to observe or rewrite a call with a missing object.
This does not apply to regular method calls outside hooks.
before receives self and the method arguments. Its return decides what happens:
nilor nothing: run the original.{ args = {...} }: replace arguments (positional or named keys). Wrong types are logged and the original args are kept.geode.skip(value): skip the original and usevalueas the return. For void methods usegeode.skip(). Skip also suppresses allaftercallbacks for that invocation.- Any other non-nil value: logged and ignored, original still runs.
geode.hook("geode.gd.GameManager:setIntGameVariable/2", {
before = function(self, key, value)
if key == "0027" and value == 0 then
return { args = { key, 1 } }
end
end,
})after receives self, the method arguments, then the return value last.
It runs only when the original C++ call runs on that invocation.
If before returns geode.skip(), neither the original nor any after callback runs.
- Return a new value to replace the return.
- Return the value you received, or
nil, to keep it. - Wrong types are logged and the original return is kept.
geode.hook("geode.gd.GameManager:getIntGameVariable/1", {
after = function(self, key, value)
if key == "0040" then return 1 end
return value
end,
})priority only matters when several hooks share the same target.
A lower value runs before first. A higher value runs after first.
On a tie, earlier registration wins for before, later registration wins for after.
geode.skip(value: any?) -> anyBuilds a skip marker for use as a before return.
The original does not run, the value becomes the return, and after callbacks do not run.
Use no argument for functions that return nothing.
type HookHandle = {
enable: (self: HookHandle) -> (boolean, string?),
disable: (self: HookHandle) -> (boolean, string?),
remove: (self: HookHandle) -> boolean,
isEnabled: (self: HookHandle) -> boolean,
}enable and disable return true on success.
When the handle is invalid or removed, they return false only.
When Geode enable or disable fails, they return false and an error string.
handle:disable()
handle:enable()
handle:remove()
print(handle:isEnabled())Per-node Lua storage. See game objects for geode.fields, m_fields, and node placement rules.
A hook is a sharp tool. Reach for a built-in API first when one exists.
- Per frame work: do not hook
CCScheduler::update. Use tasks and time (task.every) or a node'sscheduleselector. - Nodes that survive scene changes: do not re-add them from each layer's
init. UseOverlayManager. - Reading another mod's effect: check for the node or value it produces instead of hooking.
Hooking hot functions to do work an existing API already covers is a common Index rejection reason. See the Geode SDK guidelines tips and LuauAPI mod guidelines.
Luau hooks call originals through Geode's tulip wrapper for the hooked address,
so they compose with C++ $modify mods on the same method.
Bad hook returns can fault in C++ outside Lua errors. See Crash sidecar.
See Getting started for the main-thread rule. See Limits and errors for callback caps and deadlines.
tools/luau_codegen/extra_bindings/hook.dluautools/luau_codegen/emit/hooks.pytools/luau_codegen/emit/cxx_templates.pysrc/core/Config.hpp