OnGroupDeleted
OnGroupDeleted is called after an Oxide permission group has been removed from the groups data by Permission.RemoveGroup. It lets plugins react to the deletion, typically by cleaning up stored references to the group.
void OnGroupDeleted(string name)Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | The name of the Oxide permission group that is being removed. Use it to look up saved metadata (titles, team links, audit records) before the group is gone. |
Return value
The return value is ignored. Returning null or any other value has no effect on Permission.RemoveGroup; the method returns true whenever the group existed, regardless of what your hook returns.
How it works
The hook fires inside Permission.RemoveGroup(string groupName) after the group has already been removed from the group store and stripped from every user that belonged to it. Because it runs after the removal, GroupExists(name) is already false and permission.GetGroupPermissions(name) returns nothing useful — only the group name string is passed. That is enough to key any side data your plugin keeps: stored titles, per-group reward tables, team links, Discord role mappings, or WebAPI cache entries. Child groups whose parent was this group have already had their parent string cleared before the hook runs, so you cannot rely on ParentGroup lookups here. The hook fires once per successful removal; a call to RemoveGroup for a name that does not exist returns early and never reaches the hook. Removal triggered from the admin panel, another plugin calling permission.RemoveGroup, or RCON all go through the same method and therefore all fire the hook. The return value is ignored, so this is a notification-only hook.
Example plugin: Cascade-delete plugin data linked to a removed group
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Group Cleanup", "RustPluginGenerator", "1.0.0")]
[Description("Tracks administrator-marked permission groups and removes the stored record when the group is deleted.")]
public class GroupCleanup : RustPlugin
{
private const string PermUse = "groupcleanup.use";
private const string DataFileName = "GroupCleanup";
private PluginConfig config;
private StoredData data;
private class PluginConfig
{
[JsonProperty("Broadcast removals to all players")]
public bool BroadcastRemovals { get; set; } = false;
}
private class GroupRecord
{
[JsonProperty("Label")]
public string Label { get; set; }
[JsonProperty("Marked at (unix)")]
public int MarkedAt { get; set; }
}
private class StoredData
{
[JsonProperty("Tracked groups")]
public Dictionary<string, GroupRecord> Groups { get; set; } = new Dictionary<string, GroupRecord>();
}
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>
{
["Marked"] = "Group '{0}' is now tracked with label '{1}'.",
["NotMarked"] = "Group '{0}' is not tracked.",
["Removed"] = "Group '{0}' was deleted; its tracked record has been removed.",
["ListHeader"] = "Tracked groups ({0}):",
["ListEntry"] = " {0} - {1}",
["NoGroups"] = "No tracked groups.",
["NoPermission"] = "You do not have permission to use this command."
}, 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);
data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(DataFileName) ?? new StoredData();
if (data.Groups == null) data.Groups = new Dictionary<string, GroupRecord>();
}
private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(DataFileName, data);
private void OnGroupDeleted(string name)
{
if (string.IsNullOrEmpty(name) || data == null) return;
if (!data.Groups.Remove(name)) return;
SaveData();
if (config.BroadcastRemovals)
{
Server.Broadcast(Msg("Removed", null, name));
}
else
{
PrintWarning($"Tracked group '{name}' was deleted; record removed.");
}
}
[ConsoleCommand("groupcleanup.mark")]
private void CmdMark(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PermUse))
{
arg.ReplyWith(Msg("NoPermission", player.UserIDString));
return;
}
string groupName = arg.GetString(0, string.Empty);
if (string.IsNullOrEmpty(groupName) || !permission.GroupExists(groupName))
{
arg.ReplyWith($"Group '{groupName}' does not exist.");
return;
}
string label = arg.GetString(1, groupName);
data.Groups[groupName] = new GroupRecord { Label = label, MarkedAt = Facepunch.Math.Epoch.Current };
SaveData();
arg.ReplyWith(Msg("Marked", player?.UserIDString, groupName, label));
}
[ConsoleCommand("groupcleanup.list")]
private void CmdList(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PermUse))
{
arg.ReplyWith(Msg("NoPermission", player.UserIDString));
return;
}
if (data.Groups.Count == 0)
{
arg.ReplyWith(Msg("NoGroups", player?.UserIDString));
return;
}
arg.ReplyWith(Msg("ListHeader", player?.UserIDString, data.Groups.Count));
foreach (var entry in data.Groups)
{
arg.ReplyWith(Msg("ListEntry", player?.UserIDString, entry.Key, entry.Value.Label));
}
}
private void Unload()
{
SaveData();
}
}
}The plugin stores a small record for permission groups that its admins flag for cleanup: a display label and the time the group was registered through the console command groupcleanup.mark. Data lives in oxide/data/GroupCleanup.json. When OnGroupDeleted fires, the hook removes that record, writes the file only if something actually changed, and broadcasts a short notice so admins know the referenced data is gone. groupcleanup.mark <group> and groupcleanup.list are console commands available to admins; unload also saves the file.
Common use cases
Clean up per-group economy or reward tables
Delete reward multipliers, salaries or shop discounts stored in your plugin data the moment an admin removes the group, so stale config never lingers.
Sync group removals to external systems
Drop cached role mappings or cached permission snapshots keyed by group name, so a recreated group with the same name starts from scratch instead of inheriting old state.
Audit trail for permission changes
Record who removed a group and when by pairing the hook with
OnGroupCreatedandOnUserGroupRemovedinto a session log written to oxide/data.
Pitfalls
The group is already gone
The hook fires after
groupsData.Remove, soGroupExists(name),GetGroupPermissions(name)and parent-group lookups all fail. Capture anything you need from your own stored data before removal or in an earlier hook.Only fires on successful removal
Calling
RemoveGroupwith a name that does not exist returns early and never reaches the hook. Do not treat missing calls as proof the removal failed — checkGroupExistsbefore removing if you need certainty.Easy to over-log or over-broadcast
Bulk cleanup scripts can delete many groups in a short loop, producing a flood of chat messages or writes. Batch broadcast notices with a timer or only save data once at the end.
Build a plugin with OnGroupDeleted
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 OnGroupDeleted
Generate