How to make a Rust staff chat plugin (colored [STAFF] channel)
Adds a permission-gated /a command that forwards a message only to online players holding the staffchat.use permission, prefixed with a colored [STAFF] tag. It uses one ChatCommand method plus permission checks, with all output routed through Oxide lang messages.
Global chat is visible to everyone, so staff discussions get noisy fast. A dedicated staff channel keeps moderation talk private without leaving the game. This plugin registers a single chat command, /a, that only players with the staffchat.use permission can run. When they type /a <text>, the message is wrapped in a [STAFF] prefix and sent to every connected player who also holds that permission. Running /a with no text returns a usage line instead of broadcasting an empty message, and anyone without the permission gets a denial reply. The plugin keeps no data file, timer or UI, so it is stateless and safe to reload at any time. The prefix text, its color, and the all reply strings live in the language file and config, so admins can rename the tag without touching code.
The plugin code
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Staff Chat", "RustPluginGenerator", "1.0.0")]
[Description("Adds a permission-gated staff chat channel via /a with a colored prefix.")]
public class StaffChat : RustPlugin
{
private const string PermUse = "staffchat.use";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Prefix")]
public string Prefix = "[STAFF]";
[JsonProperty("Prefix Color Hex")]
public string PrefixColorHex = "#66ccff";
}
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 staff chat.",
["Usage"] = "Usage: /a <message>",
["Line"] = "{0} {1}: {2}"
}, 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);
}
[ChatCommand("a")]
private void CmdStaffChat(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
if (args == null || args.Length == 0)
{
player.ChatMessage(Msg("Usage", player.UserIDString));
return;
}
string body = string.Join(" ", args).Trim();
if (string.IsNullOrEmpty(body))
{
player.ChatMessage(Msg("Usage", player.UserIDString));
return;
}
string prefix = config.PrefixColorHex.StartsWith("#")
? $"<color={config.PrefixColorHex}>{config.Prefix}</color>"
: config.Prefix;
foreach (BasePlayer target in BasePlayer.activePlayerList)
{
if (target == null || target.IsNpc || !target.IsConnected) continue;
if (!permission.UserHasPermission(target.UserIDString, PermUse)) continue;
target.ChatMessage(Msg("Line", target.UserIDString, prefix, player.displayName, body));
}
}
private void Unload()
{
// No persistent state or timers to clean up.
}
}
}Config file
Created automatically on first load at oxide/config/StaffChat.json.
{
"Prefix": "[STAFF]",
"Prefix Color Hex": "#66ccff"
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/a <message> | Sends the message to online players with the staffchat.use permission. Without text it shows usage. | staffchat.use |
| Permission | Grants |
|---|---|
staffchat.use | Receive [STAFF] messages and use /a to send them. |
How the code works
Register the permission
In
Initthe plugin callspermission.RegisterPermission(PermUse, this)withPermUseset tostaffchat.use. The prefix must match the lowercase class name, so the const isstaffchat.use. Grant it withoxide.grant user <name> staffchat.useor to a whole group withoxide.grant group admin staffchat.use. Players without it both fail to send and never receive channel output.Handle the chat command
The
[ChatCommand("a")]attribute binds a private methodCmdStaffChat(BasePlayer player, string command, string[] args). It null-checksplayer, verifiespermission.UserHasPermission(player.UserIDString, PermUse), and if it fails sends theNoPermissionmessage. Ifargsis null or empty it sends theUsageline. Otherwise it joinsargswithstring.Join(" ", args)and trims the result, so the text the player typed is rebuilt exactly.Broadcast to staff only
The method loops over
BasePlayer.activePlayerList, skips NPCs withIsNpc, skips disconnected players withIsConnected, and skips anyone without the permission. For each remaining player it callsChatMessagewith theLinemessage formatted as<prefix> <displayName>: <message>. Because the channel shows the sender's name inline, recipients know who is speaking even though the command itself does not echo back to the sender separately.Colored prefix from config and lang
The prefix text is the config key
Prefix(default[STAFF]) and the color isPrefix Color Hex(default#66ccff). The send method wraps the tag in a Unity rich-text<color=...>tag when the hex string starts with#, otherwise it sends the tag plainly. The three reply strings live inLoadDefaultMessagesunderNoPermission,UsageandLine. Oxide chat renders<color>tags, so the tag appears colored in the chat box.Formatting and null safety
Every argument is validated before use:
playeris checked at the top,argscan be null when the command is typed alone, and target players are filtered byIsConnectedandIsNpc. The helperMsg(key, userId, args)callslang.GetMessage(key, this, userId)thenstring.Format. Passing the target'sUserIDStringlets language selection follow each recipient's client language when more than one language file is installed.Config loading and reload behavior
LoadConfigreads the config throughConfig.ReadObject<PluginConfig>(); if that returns null or throws, it prints a warning, falls back toLoadDefaultConfigand saves again.SaveConfigwrites the object back, so editingoxide/config/StaffChat.jsonand runningoxide.reload StaffChatapplies a new prefix or color. There are no timers or UI panels, soUnloadis empty.
Install it on your server
Save StaffChat.cs
Keep the file name exactly as the class name. Oxide matches them.Upload it to the plugins folder
Oxide:oxide/plugins/. Carbon:carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.Edit the config if you want
On first load the default config is written tooxide/config/StaffChat.json(Carbon:carbon/configs/StaffChat.json). After editing, reload it:oxide.reload StaffChat c.reload StaffChat # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default staffchat.use c.grant group default staffchat.use # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Prefix by staff rank
Different tags for moderators and admins.
Generate this versionDiscord bridge
Mirror staff chat to a Discord webhook.
Generate this versionReply to a specific player
Target one staff member.
Generate this version
FAQ
Why don't non-staff players see the message?
The broadcast loop filters by permission.UserHasPermission(player.UserIDString, "staffchat.use") in addition to checking IsConnected and skipping NPCs. Anyone without the permission is skipped entirely, so the message never reaches their chat. Grant the permission to any group that should see the channel.
Can I rename the [STAFF] tag or change its color?
Yes. The tag text is the Prefix config key and the color is Prefix Color Hex in oxide/config/StaffChat.json. Change them, save, and run oxide.reload StaffChat. Because the tag goes through Oxide chat, Unity rich-text works, so you can also use <b> and <i> around the text if you edit the string.
Does this work on Carbon?
Yes. Carbon loads standard Oxide plugins, and this file only uses ChatCommand, permission, lang, Config and BasePlayer members that both frameworks support. Drop it in oxide/plugins and it will compile; Carbon picks it up from the same folder.
How do I install it?
Save the file as StaffChat.cs in oxide/plugins, wait for compilation, then grant the permission with oxide.grant group admin staffchat.use. Type /a test in chat as an admin to confirm delivery. Check oxide/config/StaffChat.json for the default prefix values.
Can I stop one staff member from sending while still receiving?
Not with this version: sending and receiving use one permission. Split it by adding a second const such as staffchat.see for reception and checking staffchat.use only for the send path. The CmdStaffChat method already has the send check in place, so you only adjust which permission the receive loop tests.
Customize it for your server
Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More moderation plugins
- AFK kickTracks each connected player's movement and look direction with a repeating timer and kicks anyone who stays idle past a configured limit after aโฆOnServerInitializedOnPlayerConnected
- Chat anti-spamBlocks chat messages that exceed a rate limit or duplicate the player's previous message, and mutes repeat offenders for a configurable time.OnPlayerChat
- Chat muteA timed chat mute plugin: /mute <player> <minutes> [reason] stores a mute with an expiry in a data file, /unmute <player> clears it, andโฆOnServerInitializedOnPlayerChat
Last updated Oct 26, 2026.
Customize staff chat
Customize