Skip to content
RustPluginGenerator

OnPluginUnloaded

OnPluginUnloaded is called by Oxide after a plugin has been removed from the plugin manager, so other plugins can react when a dependency or tool plugin goes away.

Signature
void OnPluginUnloaded(Plugin plugin)

Parameters

Hook parameters
ParameterTypeDescription
pluginPluginThe Oxide plugin instance that has just been removed from the plugin manager. Use plugin.Name, plugin.Title, plugin.Version or plugin.Author to identify it.

Return value

The return value is ignored. The hook fires after the plugin has already been removed from the manager, so nothing you return can prevent or delay the unload.

How it works

The hook fires inside OxideMod.UnloadPlugin, after the target plugin is removed from the root plugin manager and only if the removed plugin was still marked as loaded. It runs for manual oxide.reload, oxide.unload, plugin reloads triggered by file changes, and shutdown sequences initiated through the same code path, so it is a reliable signal that the plugin's runtime instance is gone.

The plugin parameter is the Oxide.Core.Plugins.Plugin object itself, not a name string. Read plugin.Name (file name without extension) to compare against a dependency you track, or plugin.Title for anything user-facing. The instance is being torn down, so do not call into it or assume its hooks will still run.

There is no filtering by parameter type here: Oxide always passes the removed plugin instance. Declare Plugin exactly as given. Register this hook in a long-lived plugin (a coordinator, API, or permissions plugin) and re-check soft dependencies with plugins.Find(name) instead of caching references after the event.

Example plugin: Track and report plugins that depend on a companion plugin

DependencyWatcher.cs
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;

namespace Oxide.Plugins
{
    [Info("Dependency Watcher", "RustPluginGenerator", "1.0.0")]
    [Description("Watches key companion plugins and records when they are unloaded.")]
    public class DependencyWatcher : RustPlugin
    {
        private const string PermAdmin = "dependencywatcher.admin";

        private PluginConfig config;
        private readonly Dictionary<string, DateTime> lastUnload = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);

        private class PluginConfig
        {
            [JsonProperty("Watched plugin names")]
            public List<string> WatchedPlugins = new List<string> { "Economics", "ServerRewards", "PermissionsManager" };
        }

        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.",
                ["NoWatched"] = "No plugins are being watched.",
                ["Header"] = "Watched plugins:",
                ["Line"] = "{0} - {1}",
                ["Loaded"] = "loaded",
                ["UnloadedAt"] = "last unloaded at {0} UTC",
                ["Cleared"] = "Unload records cleared."
            }, 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(PermAdmin, this);
        }

        private void OnPluginUnloaded(Plugin plugin)
        {
            if (plugin == null || string.IsNullOrEmpty(plugin.Name)) return;
            if (!config.WatchedPlugins.Contains(plugin.Name)) return;

            lastUnload[plugin.Name] = DateTime.UtcNow;

            foreach (var player in BasePlayer.activePlayerList)
            {
                if (player != null && permission.UserHasPermission(player.UserIDString, PermAdmin))
                    player.ChatMessage(Msg("Line", player.UserIDString, plugin.Name, Msg("UnloadedAt", player.UserIDString, DateTime.UtcNow.ToString("HH:mm:ss"))));
            }
        }

        [ChatCommand("depwatch")]
        private void CmdDepwatch(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }

            if (args.Length > 0 && args[0].Equals("clear", StringComparison.OrdinalIgnoreCase))
            {
                lastUnload.Clear();
                player.ChatMessage(Msg("Cleared", player.UserIDString));
                return;
            }

            if (config.WatchedPlugins.Count == 0)
            {
                player.ChatMessage(Msg("NoWatched", player.UserIDString));
                return;
            }

            player.ChatMessage(Msg("Header", player.UserIDString));
            foreach (var name in config.WatchedPlugins)
            {
                string state;
                DateTime stamp;
                if (lastUnload.TryGetValue(name, out stamp))
                    state = Msg("UnloadedAt", player.UserIDString, stamp.ToString("yyyy-MM-dd HH:mm:ss"));
                else
                    state = Msg("Loaded", player.UserIDString);

                player.ChatMessage(Msg("Line", player.UserIDString, name, state));
            }
        }
    }
}

The plugin keeps a configurable list of watched plugin names. When any plugin is unloaded, OnPluginUnloaded compares plugin.Name (case-insensitive) against that list and records the event with a UTC timestamp. The admin command depwatch prints the current state of each watched plugin and the last time it went down, and depwatch clear resets the records. Because the check runs in memory only, it adds no cost during normal operation; the config list is the only persisted setting.

Common use cases

  • Invalidate cached plugin references

    If your plugin caches a reference obtained through [PluginReference], clear it when that plugin unloads so later calls fall back to a null check instead of calling into a dead instance.

  • Notify staff when an economy or permissions plugin drops

    Compare plugin.Name against a list of critical dependencies and send an admin-only chat or log warning so the server owner notices before players hit broken commands.

  • Clean up CUI owned by a removed plugin

    When a UI-heavy plugin unloads, destroy any of its leftover panels for active players so stale elements do not block input on the client.

Pitfalls

  • The plugin is already removed

    The hook fires after RootPluginManager.RemovePlugin, so plugins.Find(name) and dependencies on the removed instance will be gone. Do not try to call methods on the passed instance.

  • Guard against unloading your own plugin

    UnloadPlugin calls this hook while removing the target; if your handler runs for your own class name, skip work that relies on your own registered hooks, timers or UI, since they are being torn down too.

  • Name matching is exact

    plugin.Name is the file name without the .cs extension. Matching on Title or Author is fragile because those come from plugin metadata and can differ from the file name.

Build a plugin with OnPluginUnloaded

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Invalidate cached plugin references using the OnPluginUnloaded hook, with a config file and a permission
Generate this plugin

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.

Build a plugin with OnPluginUnloaded

Generate