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.
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. Two C++ overloads with the same argument count would collide on one id, so codegen picks a preferred overload or rejects the ambiguity 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.
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,
})Argument overrides also accept named keys:
geode.hook("geode.gd.GameManager:setIntGameVariable/2", {
before = function(self, key, value)
if value < 0 then
return { args = { key = key, value = 0 } }
end
end,
})A failing or timed-out before behaves like returning nothing. The original still runs.
When rewriting arguments, hook callbacks may pass nil for object pointer arguments. Normal API calls reject nil object pointers. This only applies inside hook argument overrides.
after receives self, the method arguments, then the return value last.
Void methods pass no return value, so the callback ends with the last argument.
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.
geode.hook("geode.gd.GameManager:getIntGameVariable/1", {
before = function(self, key)
if blockedKey(key) then
return geode.skip(0)
end
end,
})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.
Do not hook CCScheduler::update for per-frame work.
Use tasks and time (task.every) or a node's schedule selector.
See LuauAPI mod guidelines for when hooks are the wrong choice, and the Geode SDK guidelines tips for review rules.
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