OnUserBanned
OnUserBanned is called by Oxide after a ban entry is written to the server's user table, giving plugins the banned player's name, Steam ID, last IP, reason and expiry time.
void OnUserBanned(string playerName, string steamId, string Address, string reason, long expiry)Parameters
| Parameter | Type | Description |
|---|---|---|
playerName | string | The display name stored in the ban entry when the ban was recorded. Can be null or empty for bans issued without a known name. |
steamId | string | The banned player's Steam ID as a string (not a ulong), matching the key in the server's user table. |
Address | string | The player's last known IP address from the connected player cache, or the literal string "0" when the player is not currently tracked by the server. |
reason | string | The reason text passed to the ban command. Empty when no reason was supplied. |
expiry | long | Unix timestamp (seconds) when the ban expires, or 0 for a permanent ban. Compare against Facepunch.Math.Epoch.Current. |
Return value
The return value is ignored. Whatever you return (or don't return) has no effect on the ban, the player, or the server's user table.
How it works
The hook fires from Oxide's own user-table handler, not from a patched game method. When the server records a user as ServerUsers.UserGroup.Banned and the server has finished initializing, Oxide resolves the player through Covalence and then calls both OnPlayerBanned and OnUserBanned. The only real difference is the second argument: this hook passes the Steam ID as a string, so you can use it directly as a key for data files, permissions and economy calls.
Address comes from the connected-player cache, so it is valid for online players and falls back to "0" for bans issued on an offline ID or via banid. expiry is a raw Unix timestamp; 0 means permanent. Because the hook is called after the entry is written, you should not try to cancel the ban here. There is no return value that changes anything.
The hook also fires for bans applied through the console, RCON or another plugin, so it is a reliable single place to mirror ban state elsewhere. Declaring the parameters with the exact types above is required; there is no overload with a ulong id.
Example plugin: Record bans to a data file and notify staff
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Ban Audit Log", "RustPluginGenerator", "1.0.0")]
[Description("Records every ban to a data file and reports it to staff in chat")]
public class BanAuditLog : RustPlugin
{
private const string PermStaff = "banauditlog.staff";
private PluginConfig config;
private BanData data;
private class PluginConfig
{
[JsonProperty("Maximum stored entries")]
public int MaxEntries = 500;
[JsonProperty("Notify staff in chat")]
public bool NotifyStaff = true;
}
private class BanEntry
{
[JsonProperty("Player name")]
public string PlayerName;
[JsonProperty("Steam ID")]
public string SteamId;
[JsonProperty("IP address")]
public string Address;
[JsonProperty("Reason")]
public string Reason;
[JsonProperty("Expiry (unix, 0 = permanent)")]
public long Expiry;
[JsonProperty("Banned at (unix)")]
public long BannedAt;
}
private class BanData
{
[JsonProperty("Bans")]
public List<BanEntry> Bans = new List<BanEntry>();
}
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>
{
["BanReported"] = "BAN: {0} ({1}) - {2} - {3}",
["Permanent"] = "permanent",
["Expires"] = "expires {0}"
}, 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(PermStaff, this);
data = Interface.Oxide.DataFileSystem.ReadObject<BanData>(Name) ?? new BanData();
}
private void OnUserBanned(string playerName, string steamId, string Address, string reason, long expiry)
{
var entry = new BanEntry
{
PlayerName = playerName,
SteamId = steamId,
Address = Address,
Reason = reason,
Expiry = expiry,
BannedAt = Facepunch.Math.Epoch.Current
};
data.Bans.Add(entry);
if (data.Bans.Count > config.MaxEntries)
data.Bans.RemoveRange(0, data.Bans.Count - config.MaxEntries);
if (!config.NotifyStaff) return;
string when = expiry == 0
? Msg("Permanent")
: Msg("Expires", null, DateTimeOffset.FromUnixTimeSeconds(expiry).UtcDateTime.ToString("yyyy-MM-dd HH:mm 'UTC'"));
string line = Msg("BanReported", null, playerName ?? "unknown", steamId, reason ?? "no reason", when);
foreach (var player in BasePlayer.activePlayerList)
{
if (player != null && permission.UserHasPermission(player.UserIDString, PermStaff))
player.ChatMessage(line);
}
}
private void OnPlayerUnbanned(string playerName, ulong playerId, string reason)
{
data.Bans.RemoveAll(b => b.SteamId == playerId.ToString());
}
private void Unload()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
}
}
}The plugin listens for OnUserBanned and appends an entry to oxide/data/BanAuditLog.json holding the player name, Steam ID, IP, reason, expiry and the current time. It prints a chat line to every online player who has the banauditlog.staff permission, formatting the expiry as permanent or a date via DateTimeOffset.FromUnixTimeSeconds. Ban history is kept to the configured maximum, oldest entries dropped first. OnPlayerUnbanned is also implemented to clear any matching IP entry from the running report, and the data file is written in Unload so restarts do not lose recent bans.
Common use cases
External ban sync
Push every ban to a shared file or in-memory list that a companion plugin reads, so aliases and reasons stay consistent across the server network.
Staff audit trail
Keep a timestamped record of who was banned, by what reason and until when, without relying on a database plugin.
Link bans to player data
Use the string Steam ID to look up stored player profiles and mark the account as banned for future reference.
Pitfalls
Address is not always a real IP
Offline ID bans and console
banidproduce the literal string "0", so do not treat it as a valid address when comparing or geolocating.This is after the fact
The user table entry already exists when the hook fires, so kicking, removing or cancelling the ban from inside the hook is not supported here.
Expiry is a Unix timestamp, not a duration
0 means permanent. Passing it straight to a timespan or date formatter without checking will show 1970.
Build a plugin with OnUserBanned
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 OnUserBanned
Generate