Skip to content
RustPluginGenerator

OnClientDisconnected

OnClientDisconnected is called after a client connection has been torn down and just before the server removes that connection from its tracking. It fires at the network layer, so it runs even when no player entity is available.

Signature
void OnClientDisconnected(Network.Connection connection, string reason)

Parameters

Hook parameters
ParameterTypeDescription
connectionNetwork.ConnectionThe Network.Connection of the client that dropped. It is already marked inactive (connection.active is false) and holds the player's Steam ID via connection.userid, but the player entity may already be gone.
reasonstringThe disconnect reason string reported by the network layer, such as the client closing the connection or an error. It is free text and not guaranteed to match a fixed set of values.

Return value

The return value is ignored at this injection point. Returning null, false or nothing at all has no effect on the game, and the connection will still be removed from the server list.

How it works

The hook fires inside Network.Server.OnDisconnected, after the connection has been marked inactive and after the framework's own connection callback ran, but before the server drops the connection from its list. Because it is placed at the transport level, it triggers on any disconnect: normal logout, timeouts, kicks and Steam/auth failures alike.

The connection argument gives you userid (the Steam ID) and active; the player object may already be invalid, so resolve names through IDs rather than BasePlayer.FindByID. The reason string is whatever the network layer reports and should be treated as informational only.

Use this hook when you specifically want the raw transport-level signal, for example to log every drop including ones that never reached a spawned player. For gameplay-level cleanup (inventory restoration, vanish state, team bookkeeping) prefer OnPlayerDisconnected, which carries the real BasePlayer. Declaring the parameter as Network.Connection is required here; there is no more specific subtype to narrow with. Keep the handler light because every disconnect passes through it.

Example plugin: Log every client disconnect with reason

DisconnectAudit.cs
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Disconnect Audit", "RustPluginGenerator", "1.0.0")]
    [Description("Records recent client disconnects with their reported reason and exposes them to admins")]
    public class DisconnectAudit : RustPlugin
    {
        private const string PermView = "disconnectaudit.view";

        private PluginConfig config;
        private readonly List<Entry> recent = new List<Entry>();

        private class PluginConfig
        {
            [JsonProperty("Maximum entries kept in memory")]
            public int MaxEntries = 50;

            [JsonProperty("Ignore entries without a Steam ID")]
            public bool IgnoreAnonymous = true;
        }

        private class Entry
        {
            public string Time;
            public string UserId;
            public string Reason;
        }

        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>
            {
                ["NoEntries"] = "No disconnects recorded yet.",
                ["Entry"] = "{0} | {1} | {2}",
                ["Cleared"] = "Disconnect log cleared.",
                ["Denied"] = "You do not have permission to use this command."
            }, this);
        }

        private string Msg(string key, string userId = null, params object[] args)
        {
            return string.Format(lang.GetMessage(key, this, userId), args);
        }

        private void Init()
        {
            permission.RegisterPermission(PermView, this);
        }

        private void OnClientDisconnected(Network.Connection connection, string reason)
        {
            if (connection == null) return;

            string userId = connection.userid.ToString();
            if (config.IgnoreAnonymous && (string.IsNullOrEmpty(userId) || userId == "0")) return;

            recent.Add(new Entry
            {
                Time = DateTime.UtcNow.ToString("HH:mm:ss"),
                UserId = userId,
                Reason = string.IsNullOrEmpty(reason) ? "unknown" : reason
            });

            int overflow = recent.Count - Mathf.Max(1, config.MaxEntries);
            if (overflow > 0) recent.RemoveRange(0, overflow);
        }

        [ConsoleCommand("disconnectaudit.list")]
        private void CmdList(ConsoleSystem.Arg arg)
        {
            if (arg == null) return;
            if (!arg.IsAdmin && (arg.Player() == null || !permission.UserHasPermission(arg.Player().UserIDString, PermView)))
            {
                arg.ReplyWith(Msg("Denied"));
                return;
            }

            if (recent.Count == 0)
            {
                arg.ReplyWith(Msg("NoEntries"));
                return;
            }

            for (int i = recent.Count - 1; i >= 0; i--)
            {
                Entry e = recent[i];
                arg.ReplyWith(Msg("Entry", null, e.Time, e.UserId, e.Reason));
            }
        }

        [ConsoleCommand("disconnectaudit.clear")]
        private void CmdClear(ConsoleSystem.Arg arg)
        {
            if (arg == null) return;
            if (!arg.IsAdmin && (arg.Player() == null || !permission.UserHasPermission(arg.Player().UserIDString, PermView)))
            {
                arg.ReplyWith(Msg("Denied"));
                return;
            }

            recent.Clear();
            arg.ReplyWith(Msg("Cleared"));
        }

        private void Unload()
        {
            recent.Clear();
        }
    }
}

The plugin records each transport-level disconnect into an in-memory ring buffer of recent entries and prints them on demand. OnClientDisconnected converts the connection's userid into a string, filters out entries not yet authenticated when the config says so, and stores the timestamp, ID, and reason. A console command disconnectaudit.list shows the last N entries to admins, and disconnectaudit.clear wipes the buffer. Nothing is written to disk, so the log is transient and cheap per disconnect, which matters because the hook fires for every drop including timeouts and Steam failures.

Common use cases

  • Monitor disconnects and reasons

    Keep a rolling list of recent drops with their reported reasons so admins can spot patterns like mass timeouts or repeated auth failures.

  • Alert on suspicious disconnects

    Compare a player's last known state against their connection ID and send a Discord-style broadcast (via another plugin) when a player drops unexpectedly during combat.

  • Release external session data

    If another plugin tracks temporary resources keyed by Steam ID, this hook lets you free them even when the player entity was never fully spawned.

Pitfalls

  • Player object may not exist

    The hook fires at the network layer, so BasePlayer.FindByID can return null. Work from the connection's userid and guard any lookup.

  • Overlapping with OnPlayerDisconnected

    Both hooks fire on a normal logout. Doing the same cleanup in both will run twice; pick the one that has the data you actually need.

  • Reason text is not standardized

    The reason string comes straight from the network layer and varies by cause. Do not branch on exact matches; log it or treat it as a hint only.

Build a plugin with OnClientDisconnected

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

Prompt: Monitor disconnects and reasons using the OnClientDisconnected 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 OnClientDisconnected

Generate