OnPlayerKicked
OnPlayerKicked is called after the server has already kicked a connected player, for admin/plugin kicks through BasePlayer.Kick and for EAC anti-cheat kicks.
void OnPlayerKicked(BasePlayer player, string reason, bool reserveSlot)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The BasePlayer being kicked. When the hook is fired from the EAC overload this parameter is a Network.Connection instead, so a BasePlayer-typed method is skipped for anticheat kicks. |
reason | string | The kick reason string. For admin/plugin kicks this is the text you or another plugin passed to Kick; for EAC kicks the string is prefixed with EAC. |
reserveSlot | bool | Whether the kicked player's connection keeps a reserved slot (canReserveSlot). Always false for plugin-driven kicks unless you pass true. |
Return value
The return value is ignored at both injection sites; the original method returns void and runs to completion. Return null or nothing to keep behavior unchanged.
How it works
The hook fires at the end of BasePlayer.Kick, after Net.sv.Kick(connection, reason) has been sent, and again from EACServer.OnClientActionRequired after an anticheat kick is issued. Because the hook runs after the kick, you cannot cancel it; it is a notification. player is the kicked BasePlayer, reason is the reason string (EAC kicks send EAC: <details>), and reserveSlot mirrors the canReserveSlot flag written to the connection before the kick.
Two overloads exist. The primary one is void OnPlayerKicked(BasePlayer player, string reason, bool reserveSlot). The EAC path calls OnPlayerKicked(Network.Connection connection, string reason) instead. Oxide only invokes a method when every argument matches the declared parameter types, so a BasePlayer-typed method never runs for EAC kicks and a Connection-typed method never runs for normal kicks. Declare both if you need full coverage.
The hook also fires for admin kick console commands and for plugins that call player.Kick(...), so use it for auditing, logging to a data file, or triggering post-kick follow-up such as Discord relays via another plugin.
Other signatures
Oxide also calls OnPlayerKicked from other places in the game with these parameter types. Declare the overload you need, or several:
void OnPlayerKicked(Network.Connection connection, string toString)
Example plugin: Log all kicks to a data file
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Kick Audit Log", "RustPluginGenerator", "1.0.0")]
[Description("Records every player kick (admin, plugin and EAC) into a rolling data file.")]
public class KickAuditLog : RustPlugin
{
private const string PermView = "kickauditlog.view";
private const string DataFile = "KickAuditLog";
private PluginConfig config;
private List<Entry> entries;
private class PluginConfig
{
[JsonProperty("Maximum stored entries")]
public int MaxEntries = 200;
}
private class Entry
{
[JsonProperty("Username")]
public string Name;
[JsonProperty("User ID")]
public string UserId;
[JsonProperty("Reason")]
public string Reason;
[JsonProperty("Reserved slot")]
public bool ReserveSlot;
[JsonProperty("UTC time")]
public string Utc;
}
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>
{
["NoAccess"] = "You do not have permission to use this command.",
["NoEntries"] = "No kicks have been recorded yet.",
["Header"] = "Last {0} kicks:",
["Line"] = "{0} ({1}) - {2} [{3}]"
}, 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(PermView, this);
entries = Interface.Oxide.DataFileSystem.ReadObject<List<Entry>>(DataFile) ?? new List<Entry>();
}
private void OnPlayerKicked(BasePlayer player, string reason, bool reserveSlot)
{
if (player == null) return;
Record(player.displayName, player.UserIDString, reason, reserveSlot);
}
private void OnPlayerKicked(Network.Connection connection, string reason)
{
if (connection == null) return;
Record(connection.username ?? "unknown", connection.userid.ToString(), reason, false);
}
private void Record(string name, string userId, string reason, bool reserveSlot)
{
if (entries == null) return;
entries.Add(new Entry
{
Name = name,
UserId = userId,
Reason = reason ?? string.Empty,
ReserveSlot = reserveSlot,
Utc = DateTime.UtcNow.ToString("u")
});
int cap = config?.MaxEntries > 0 ? config.MaxEntries : 200;
while (entries.Count > cap) entries.RemoveAt(0);
Interface.Oxide.DataFileSystem.WriteObject(DataFile, entries);
}
[ChatCommand("kicklog")]
private void CmdKickLog(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermView))
{
player.ChatMessage(Msg("NoAccess", player.UserIDString));
return;
}
if (entries == null || entries.Count == 0)
{
player.ChatMessage(Msg("NoEntries", player.UserIDString));
return;
}
player.ChatMessage(Msg("Header", player.UserIDString, entries.Count));
int start = Mathf.Max(0, entries.Count - 10);
for (int i = start; i < entries.Count; i++)
{
Entry e = entries[i];
player.ChatMessage(Msg("Line", player.UserIDString, e.Name, e.UserId, e.Reason, e.Utc));
}
}
private void Unload()
{
Interface.Oxide.DataFileSystem.WriteObject(DataFile, entries);
entries = null;
}
}
}The plugin keeps a rolling audit trail of kicks in oxide/data/KickAuditLog.json. Two hook overloads are declared: OnPlayerKicked(BasePlayer player, string reason, bool reserveSlot) handles admin and plugin kicks, and OnPlayerKicked(Network.Connection connection, string reason) catches EAC anti-cheat kicks where only a connection is available. Each entry records the username, user id, reason, UTC timestamp and whether the connection kept a reserved slot. MaxEntries in the config caps the list size so the file does not grow without bound. Admins with kickauditlog.view can run /kicklog to print the most recent entries in chat. A short Entry class is used purely as the persisted data shape.
Common use cases
Audit trail for staff actions
Record every admin kick with the reason so you can review abuse after the fact. Combine with OnPlayerBanned to separate kicks from bans.
Notify a Discord relay
If you run a Discord webhook plugin that exposes an Oxide API, call it from OnPlayerKicked to post the username and reason into a staff channel.
Track repeat kick offenders
Increment a per-user counter in a data file and surface users who are kicked repeatedly, useful for spotting problem players before they get banned.
Pitfalls
One method signature does not catch both sources
A BasePlayer-typed method never runs for EAC kicks and a Connection-typed method never runs for normal kicks. Declare both overloads if you want full coverage.
The kick has already happened
The hook fires after Net.sv.Kick is sent. You cannot prevent or delay the kick from here; use CanBypassQueue or other pre-check hooks for that.
The player may not be fully disconnected yet
Do not assume OnPlayerDisconnected has already fired. Accessing player.inventory or held entities is safe, but the world state may be mid-teardown.
Build a plugin with OnPlayerKicked
Describe what should happen and get a compiled plugin that uses this hook.
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 OnPlayerKicked
Generate