Oxide PluginReference: Calling Other Plugins from a Rust Plugin
Add a `[PluginReference]` field of type `Plugin` to your class and Oxide fills it with the loaded plugin of the same name, or null when that plugin is not loaded. Call its methods with `Call` or `Call<T>`, always null-check the field first, and use `// Requires: Name` comments when the plugin cannot work without the dependency.
5 min readUpdated Oct 26, 2026
[PluginReference] is an Oxide attribute that you put on a field of type Plugin; Oxide fills the field with the loaded plugin that has the same name as the field, or leaves it null if that plugin is not loaded. You then talk to the other plugin through Call and Call<T>. Carbon loads Oxide plugins and supports the same pattern.
Declaring a reference
You need using Oxide.Core.Plugins; for the Plugin type and the attribute.
[PluginReference] private Plugin Economics;The field name is the plugin name. A field called Economics refers to the plugin whose name is Economics, and Kits refers to Kits. Names are about the plugin itself, not your own class. The field type is always Plugin, never the other plugin's class, because plugins are compiled separately and cannot see each other's types.
Oxide assigns these fields as plugins load and unload. Do not assign them yourself, and do not rely on them inside a field initializer or constructor. They are safe to use from Loaded onward, and most reliably from OnServerInitialized, when load order no longer matters.
Always null-check the reference
If the target plugin is not installed, failed to compile, or was unloaded, the field is null. Calling a method on it throws a NullReferenceException (see failed-to-call-hook-nullreferenceexception). Two safe patterns:
Economics?.Call("Deposit", player.UserIDString, 100.0);if (Economics == null)
{
// fall back or tell the player the feature is unavailable
return;
}Use the conditional form when skipping the action is acceptable. Use the explicit check when you need a fallback, for example paying nothing and telling the player why.
Call and Call<T>
Plugin.Call(string hook, params object[] args) runs the named method on that plugin and returns its result as object. Call<T> does the same and casts the result to T.
| Form | Returns | Use when |
|---|---|---|
plugin.Call("Name", args) | object | you ignore the result or test it for null |
plugin.Call<double>("Balance", id) | double | the result type is documented |
plugin.Call<bool>("Withdraw", id, 50.0) | bool | the method returns a success flag |
Things that commonly go wrong:
- Argument types. Oxide matches by the parameter types of the target method. A
doubleparameter and anintargument may not match, so pass100.0rather than100when the API takes a double. Player IDs are often expected as strings (UserIDString) but some plugins takeulongorBasePlayer. Read the API documentation of the plugin you call. - Null results.
Callreturns null if the method does not exist, if the arguments do not fit, or if the method returns null or nothing. When usingCall<T>with a value type likedouble, a missing method gives you the default value (0) or fails, so treat the result with care and check the plugin loaded first. - API names differ between versions. Method names are a convention between plugin authors, not something the game enforces. Pin your expectations to the documentation of the version your server runs.
Example: depositing into an economy plugin
This complete plugin gives scrap-free rewards by paying a configurable amount into Economics when a player uses a chat command, and falls back politely when Economics is missing.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core.Plugins;
namespace Oxide.Plugins
{
[Info("Pay Bonus", "RustPluginGenerator", "1.0.0")]
[Description("Pays a configurable bonus into the Economics plugin via a chat command")]
public class PayBonus : RustPlugin
{
private const string PermUse = "paybonus.use";
[PluginReference] private Plugin Economics;
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Bonus amount")]
public double Amount = 100.0;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["NoPermission"] = "You do not have permission to use this command.",
["NoEconomy"] = "The economy system is currently unavailable.",
["Paid"] = "You received {0} coins."
}, this);
}
private string Msg(string key, string userId = null, params object[] args) =>
string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermUse, this);
}
[ChatCommand("bonus")]
private void CmdBonus(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
if (Economics == null)
{
player.ChatMessage(Msg("NoEconomy", player.UserIDString));
return;
}
Economics.Call("Deposit", player.UserIDString, config.Amount);
player.ChatMessage(Msg("Paid", player.UserIDString, config.Amount));
}
}
}Economics-style plugins usually accept the player ID as a string and the amount as a double, which is why the code passes UserIDString and a double config value. If you target a different economy plugin, check its documented API. See also server-currency for a simple currency recipe and /plugins/economy for plugins in that category.
Exposing your own API
Any method in your plugin can be called by name from another plugin through Call. Keep API methods stable and simple:
private bool IsVip(string userId)
{
return permission.UserHasPermission(userId, PermVip);
}
private double GetBonusMultiplier(string userId)
{
return IsVip(userId) ? 2.0 : 1.0;
}Another plugin then uses MyPlugin?.Call<bool>("IsVip", player.UserIDString). Guidelines for a good API:
- Use primitive types (
string,double,int,bool,ulong) or common game types as parameters and return values. - Do not rename the method after other plugins depend on it.
- Validate arguments; the caller may pass anything.
- Document the exact names and types in your plugin description page.
Avoid naming an API method the same as a real hook unless you intend it to behave as that hook, since Oxide dispatches hooks by method name.
Reacting to plugins loading and unloading
OnPluginLoaded and OnPluginUnloaded fire with the Plugin object when any plugin changes state. Use them to refresh cached data or toggle a feature:
private void OnPluginLoaded(Plugin plugin)
{
if (plugin == null) return;
if (plugin.Name == "Economics")
Puts("Economics loaded, payouts enabled");
}
private void OnPluginUnloaded(Plugin plugin)
{
if (plugin == null) return;
if (plugin.Name == "Economics")
Puts("Economics unloaded, payouts paused");
}These fire rarely, so logging inside them is acceptable. Because the [PluginReference] field updates itself, you usually only need these hooks for side effects, not to reassign the field.
Custom hooks with Interface.CallHook
Interface.CallHook("Name", args) (with using Oxide.Core;) calls every loaded plugin that has a method of that name, exactly like built-in hooks. Use it when several plugins may care about an event, or when you want others to be able to veto something.
private void GiveBonus(BasePlayer player, double amount)
{
// other plugins may implement: private object OnBonusGive(BasePlayer player, double amount)
if (Interface.CallHook("OnBonusGive", player, amount) != null)
return; // some plugin returned a non-null value: cancel
Economics?.Call("Deposit", player.UserIDString, amount);
}For a hook you invent, you decide what a non-null return means, so document it: "return any non-null value to cancel". Remember that arguments are matched by type, so listeners declaring a more specific parameter type are skipped when the argument is a different type. See oxide-hooks-explained for dispatch details.
Choose between the two mechanisms like this:
| Need | Use |
|---|---|
| Ask one specific plugin for data or an action | plugin.Call / Call<T> |
| Announce an event to anyone listening | Interface.CallHook |
| Let others cancel your action | Interface.CallHook and check for non-null |
Hard dependencies with // Requires:
If your plugin cannot work at all without another plugin, add a comment at the top of the file:
// Requires: EconomicsOxide reads these comments before compiling. When the required plugin is missing, your plugin does not load and Oxide logs a missing-dependency message instead; see plugin-requires-missing-dependency for how to read and fix it. When the required plugin loads later, Oxide loads your plugin then. When the required plugin is unloaded or reloaded, plugins that require it are affected too.
Use // Requires: only for real hard dependencies. For optional integrations, keep the [PluginReference] field and null checks, so your plugin keeps working alone. Both can coexist, but a required plugin makes the null check redundant.
Troubleshooting
- Reference is always null. Check that the other plugin appears in
oxide.pluginsand that its name matches your field name exactly, including capitalization. - Call does nothing. The method name or argument types probably do not match. Test with a one-off console command that prints the result of
Call<T>. - NullReferenceException on a call. You skipped a null check, or a hook ran while the other plugin was unloaded. See debugging-rust-plugins.
- Works after reload but not at server start. Do not call the other plugin from
Init; wait for OnServerInitialized.
For plugin-side basics such as permissions used in the example, read oxide-permissions-and-groups. To build integrations quickly, you can describe them to the plugin generator.
FAQ
Does the PluginReference field name have to match the plugin name?
Yes. Oxide matches the field name to the plugin name, so a field called Economics points to the plugin named Economics. The type is always Plugin, not the other plugin's class. If you want a different field name, check the attribute documentation for your Oxide version; the safe approach is to use the exact plugin name.
Why does Call return null even though the plugin is loaded?
Call returns null when the target plugin has no method with that name, when the arguments do not fit the method's parameters, or when the method itself returns null or void. A null result is ambiguous, so check the plugin's documentation for its return convention and the exact parameter types, such as double versus int.
Can I call a private method of another plugin?
Oxide's Call looks up methods by name on the target plugin, and plugins commonly expose API methods as private or public methods named by convention, like Deposit. Treat a method as API only if its author documents it. Undocumented methods can change or disappear in any update without warning.
What is the difference between Interface.CallHook and Plugin.Call?
Plugin.Call targets one specific plugin object. Interface.CallHook broadcasts a hook name to every loaded plugin that implements a method with that name, the same way built-in hooks like OnPlayerDeath work. Use Call for a direct request and CallHook for events other plugins may want to react to or cancel.
Will my plugin reload when the plugin it references reloads?
Your plugin is not necessarily reloaded when an optional reference reloads, but Oxide updates the field to point at the new instance. Plugins marked with // Requires: are reloaded when the required plugin changes. Use OnPluginLoaded and OnPluginUnloaded if you need to react to those changes.
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