Oxide Hooks Explained: How Rust Plugin Hooks Work
An Oxide hook is a method name that Oxide calls on your plugin when something happens on the Rust server, such as a player connecting or an entity taking damage. Oxide patches the game code to call Interface.CallHook, matches your methods by name, picks overloads by parameter type, and uses the return value to decide whether the default behavior continues.
8 min readUpdated Oct 26, 2026
An Oxide hook is a plugin method whose name matches an event on the Rust server. When that event happens, Oxide calls every loaded plugin that has a method with that name, passes the event's arguments, and in many cases lets the return value change what the game does next. Everything else in plugin development, from permissions to UI, is driven by hooks.
How hooks get from the game to your plugin
Oxide modifies the Rust server's code when it starts. At specific places in game methods, it inserts a call to Interface.CallHook("HookName", arguments...). When the game reaches that point, the call goes to Oxide, which walks through the loaded plugins and invokes matching methods.
For example, the hook OnPlayerConnected fires when a player finishes connecting, and CanLootEntity fires in the game's loot-opening path before the player is allowed to open the container. You never write the Interface.CallHook line yourself for game events; you only write the receiving method.
Because the call sites are inserted into game code, a hook can break after a Rust update if the game method it sits in changes. That is why the same hook can behave differently between wipes. See fix Rust plugins after an update and the monthly hook change reports.
How Oxide matches methods to hooks
Matching by name
Oxide finds plugin methods by name. A method called OnPlayerConnected receives the OnPlayerConnected hook. The method can be private; hook methods do not need to be public. Spelling and capitalization must match exactly, since a typo compiles without error and simply never runs.
private void OnPlayerConnected(BasePlayer player)
{
if (player == null) return;
PrintToChat(player.displayName + " joined");
}Parameter types select overloads
A hook can be implemented several times in one plugin with different parameter types. Oxide calls the methods whose declared parameter types accept the arguments, using an instance-of check on each argument. If an argument is not an instance of the declared type, that method is skipped for that call.
This is useful for filtering. The hook OnEntityTakeDamage passes a BaseCombatEntity as the first argument. If you declare it as BasePlayer, Oxide only calls your method when the damaged entity is a player:
private object OnEntityTakeDamage(BasePlayer player, HitInfo info)
{
// Runs only when the victim is a BasePlayer
return null;
}
private object OnEntityTakeDamage(BuildingBlock block, HitInfo info)
{
// Runs only when the victim is a BuildingBlock
return null;
}This replaces a manual as cast and an early return, and it avoids running your code for entities you do not care about. The flip side is that a type declared too narrowly silently drops calls. If a hook seems dead, loosen the type and check the argument manually.
Declaring a type that never matches (for example BasePlayer for a hook that always passes an item) means the method is never called and nothing is logged.
Fewer parameters are fine
A hook method may declare fewer parameters than the hook supplies. Oxide passes the leading arguments and drops the rest:
private void OnEntityDeath(BaseCombatEntity entity)
{
// HitInfo is also passed by the hook, but we do not need it
}You cannot declare more parameters than the hook provides, and you cannot skip one in the middle. If you need the third argument, declare the first three.
Parameter names are free
Only the types and order matter. You can call the player parameter player, p or attacker. Use the names from the hook page as a convention so other developers can read your code.
Return values
What your method returns tells Oxide whether to continue with default behavior. The details depend on how the hook was patched into the game, so always read the hook's page. These general patterns hold.
Returning null
Returning null (or declaring the method void) means "no opinion". The game continues normally and other plugins still get their say. For object-returning hooks, return null; at the end of the method is the standard way to stay out of the way.
Returning non-null
For many Can* and On* hooks, the game code is patched so that if the hook returns anything non-null, the original action is skipped. That includes both true and false. The following hooks work this way: CanBuild, CanLootEntity, OnEntityTakeDamage, OnStructureUpgrade and OnDispenserGather.
So return false; and return true; have the same effect on these hooks: the action is cancelled. A common mistake is writing return true; expecting to "allow" something. To allow, return null.
private object CanLootEntity(BasePlayer player, StorageContainer container)
{
if (player == null || container == null) return null;
if (permission.UserHasPermission(player.UserIDString, "mylock.bypass")) return null;
return false; // any non-null value blocks the looting
}Hooks where the value itself matters
A few hooks are patched so the returned value is used as the result. On code locks, the hook CanUseLockedEntity treats a returned true as "open" and false as "deny". For OnItemCraft, returning a bool ends the crafting request. Treat these as exceptions and do not assume that returning true forces an action on any other hook.
Overriding values
Some hooks let you replace a value, for instance OnMaxStackable, where the integer you return becomes the stack limit. These hooks document the expected return type; returning the wrong type is ignored or can cause an exception.
Conflicts between plugins
Oxide calls every plugin that implements the hook. If two plugins return non-null values that differ, Oxide logs a conflict message naming both plugins and uses one of the values. If one returns null and another returns a value, the value is used. This means a block from one plugin beats a "do nothing" from another, which is usually what you want, but two plugins that each try to override the same value produce unpredictable results. When you see a conflict warning, check which plugins implement that hook and decide which one should own the behavior.
Hook categories
Hooks fall into a few groups, which helps when you search for one.
| Category | Examples | Typical use |
|---|---|---|
| Plugin lifecycle | Init, Loaded, Unload, OnServerInitialized | Setup and cleanup |
| Server events | OnServerSave, OnNewSave | Saving data, wipe detection |
| Player events | OnPlayerConnected, OnPlayerDisconnected, OnPlayerRespawned, OnPlayerSleepEnded, OnPlayerDeath | Join messages, kits, stats |
| Chat and commands | OnPlayerChat, OnPlayerCommand | Filters, mutes, aliases |
| Entity events | OnEntitySpawned, OnEntityKill, OnEntityDeath, OnEntityBuilt, OnEntityTakeDamage | Limits, damage rules |
| Permission checks (Can*) | CanBuild, CanLootEntity | Allowing or blocking actions |
| Items and gathering | OnItemCraft, OnItemCraftFinished, OnDispenserGather, OnCollectiblePickup | Rates, crafting rules |
| Plugin and permission events | OnPluginLoaded, OnPluginUnloaded, OnUserPermissionGranted | Reacting to other plugins |
Naming helps as well. Hooks starting with Can generally ask whether something is allowed and are meant to be cancelled. Hooks starting with On report that something happened or is about to, and some of them can also be cancelled. Never rely on the prefix alone; read the hook page.
Plugin lifecycle order
Lifecycle hooks run in a fixed order when a plugin loads:
- Init runs first. Register permissions and commands here. Other plugins and the server may not be ready, so do not touch the world.
- The configuration and language files are loaded. LoadDefaultConfig and LoadDefaultMessages are used when no saved file exists.
- Loaded runs once the plugin is fully loaded.
- OnServerInitialized runs once the server is up. If the plugin loads on a server that is already running (for example after
oxide.reload), it is called immediately withinitialset tofalse. - Unload runs when the plugin is unloaded, reloaded or the server shuts down.
private void Init()
{
permission.RegisterPermission("example.use", this);
}
private void OnServerInitialized(bool initial)
{
foreach (var player in BasePlayer.activePlayerList)
if (player != null) SetupPlayer(player);
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
if (player != null) CuiHelper.DestroyUi(player, "ExamplePanel");
}Put anything that scans entities or players in OnServerInitialized, because it also covers the reload case. Use OnPlayerConnected for players who join later. For the rest of the setup, see how to make a Rust plugin.
Finding the right hook
Start from the behavior you want to change, then look for the game event closest to it.
- Describe the event in one sentence, such as "when a player opens a box". That points to the loot hooks: CanLootEntity (can be blocked) and OnLootEntity (already happening).
- Prefer the narrowest hook. OnDispenserGather covers trees and ore, OnCollectiblePickup covers pickups like hemp. Using a general entity hook for gathering works but costs more.
- Check whether you need to block or to react. Blocking needs a hook that fires before the action, usually a
Can*hook. Reacting can use a hook that fires after. - Read the signature on the hook page and copy the parameter types exactly.
- Look at recipes. The gather rate multiplier and first blood recipes show hooks used in real plugins, and Rust damage handling covers the damage pipeline in detail.
If a hook seems to do nothing, add a one-time Puts at startup to confirm the plugin loaded, then test with a temporary log inside a rare hook. Do not log in frequent hooks. More techniques are in debugging Rust plugins.
Performance of hot hooks
Some hooks fire constantly: OnEntityTakeDamage, OnEntitySpawned, OnDispenserGather, OnPlayerInput and OnTick. Every loaded plugin that implements them adds to the cost of each call, and the server thread waits for all of them.
Guidelines:
- Exit early. Check the cheap conditions first (null, type, permission) and return before doing any real work.
- Use typed overloads so Oxide skips entities you do not care about.
- Do not allocate in a loop, do not search all entities (
BaseNetworkable.serverEntities) inside a hot hook, and do not callPutsorPrintWarningin them. - Cache lookups in a dictionary or set instead of recomputing per call.
- Prefer a timer over OnTick when you only need something every few seconds. See Rust plugin timers.
Oxide reports slow hooks. A hook that takes longer than 100 ms logs Calling '<Hook>' on '<Name> v<Version>' took <N>ms, with [GARBAGE COLLECT] appended if a garbage collection happened during the call. The meaning and fixes are described in the hook performance warning and Rust plugin performance.
Common mistakes
- Returning
trueto allow an action. On mostCan*hooks any non-null value cancels. Returnnullto allow. - Declaring types too narrowly. A
BasePlayerparameter skips every non-player call, which is only right if that is what you want. - Skipping null checks.
HitInfo,info.InitiatorPlayeranditem.infocan be null. ANullReferenceExceptionis logged as Failed to call hook. - Wrong cast. Casting a hook argument to the wrong type produces InvalidCastException; prefer typed overloads or
aswith a null check. - Using a removed hook. Old hooks may stop firing after an update. See deprecated hook warnings.
- Doing world work in
Init. Move it toOnServerInitialized.
Where to go next
Pick a hook page for its exact signature and return behavior, then build a small plugin around it. The AI plugin generator can produce a starting file for a given hook, and the Oxide vs Carbon guide covers differences in how the two platforms handle hooks.
FAQ
Can I declare a hook method with fewer parameters than the hook passes?
Yes. Oxide allows a method to declare fewer parameters than the hook supplies, and the extra arguments are dropped. For example, OnEntityTakeDamage(BaseCombatEntity entity) is valid if you do not need the HitInfo. You cannot reorder parameters or skip one in the middle; the declared parameters must match the leading arguments of the hook.
Why does my hook method never run and no error appears?
Usually the name is misspelled or a parameter type is more specific than the actual arguments, so Oxide skips the call silently. Check the exact hook name and signature on its hook page. Also confirm the plugin loaded by looking for the Loaded plugin line in the console or running oxide.plugins.
Do hooks I invent myself work?
A method with an unknown name compiles fine but is never called unless something calls that hook. Plugins can fire their own hooks with Interface.CallHook("OnMyEvent", arg), and other plugins can then implement them. The game itself only fires the hooks that Oxide patched into it.
What happens when a hook throws an exception?
Oxide catches it and logs Failed to call hook '<Hook>' on plugin '<Name> v<Version>' with the exception type, message and stack trace. The game keeps running and the plugin stays loaded. Other plugins still receive the hook, but the failing method's return value is lost for that call.
Are hooks the same on Carbon?
Carbon loads Oxide plugins and supports the common Oxide hook names, so most plugins run unchanged. Carbon implements hooks with its own patching, so rarely used or recently changed hooks can differ. Test the plugin on the platform you run and check the monthly hook reports on the updates page.
More guides
- CovalencePlugin vs RustPlugin: Differences and Which to Use4 min read
- How to Debug a Rust Oxide Plugin: Compile Errors, Exceptions and Slow Hooks10 min read
- How to Give Items to Players in a Rust Plugin (Oxide/Carbon)5 min read
- How to Install Oxide on a Rust Dedicated Server (Windows and Linux)7 min read
Skip the C#: describe your plugin
Open generator