Skip to content
RustPluginGenerator

OnUserChat

OnUserChat is called by Oxide's chat bridge when a connected player sends a chat message, right after OnPlayerChat, and receives the player as an IPlayer alongside the text.

Signature
object OnUserChat(IPlayer iplayer, string message)

Parameters

Hook parameters
ParameterTypeDescription
iplayerIPlayerThe chatting player wrapped as an Oxide IPlayer (Covalence). Use it for UserId, Name, IsAdmin and to call IPlayer members; the underlying BasePlayer is available through the object interface when you need game-specific members.
messagestringThe chat line after Oxide has stripped rich text, with chat-command input already handled before this hook runs. It does not include the player name or channel prefix.

Return value

Returning a non-null value blocks the local chat display for that message. Returning null lets the message through. The return value only matters when the plugin's OnPlayerChat also returned null; otherwise the OnPlayerChat result wins, because Oxide prefers that hook's return value.

How it works

Oxide calls OnUserChat from its chat handling routine. The sequence is: empty or literal text messages are dropped, a message starting with the chat command prefix is routed to the command handler and never reaches the hook, rich text is escaped, and only then are OnPlayerChat and OnUserChat fired. If the player is not connected, the call goes to OnPlayerOfflineChat instead, so OnUserChat only fires for a live BasePlayer. You get two arguments: the player as an Oxide IPlayer and the final, escaped message string. Declaring IPlayer is the only supported parameter type here; Oxide matches the hook by checking the argument is an IPlayer instance, and a different declared type makes the call skip your method. Because this hook dispatches through Oxide rather than a patched game method, it works the same under Carbon. Messages that begin with the configured chat command prefix (usually /) are consumed before this point, so you cannot intercept commands with it.

Example plugin: Mute chatters and filter banned words

ChatModerator.cs
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("ChatModerator", "RustPluginGenerator", "1.0.0")]
    [Description("Filters banned words and enforces temporary chat mutes")]
    public class ChatModerator : RustPlugin
    {
        private const string PermModerate = "chatmoderator.moderate";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Blocked words")]
            public List<string> BlockedWords = new List<string> { "badword", "spamlink" };

            [JsonProperty("Default mute minutes")]
            public int DefaultMuteMinutes = 30;

            [JsonProperty("Admins bypass word filter")]
            public bool AdminsBypass = true;
        }

        private class StoredData
        {
            [JsonProperty("Mutes")]
            public Dictionary<string, DateTime> Mutes = new Dictionary<string, DateTime>();
        }

        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>
            {
                ["Muted"] = "You are muted until {0} UTC.",
                ["Filtered"] = "Your message contains a blocked word.",
                ["MuteSet"] = "Muted {0} for {1} minutes.",
                ["MuteCleared"] = "Cleared the mute on {0}.",
                ["NotMuted"] = "That player is not muted."
            }, 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(PermModerate, this);
        }

        private void OnServerInitialized()
        {
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private object OnUserChat(IPlayer iplayer, string message)
        {
            if (iplayer == null || string.IsNullOrEmpty(message)) return null;

            string userId = iplayer.Id;
            if (data.Mutes.TryGetValue(userId, out DateTime until))
            {
                if (until > DateTime.UtcNow)
                {
                    iplayer.Reply(Msg("Muted", userId, until.ToString("HH:mm")));
                    return false;
                }
                data.Mutes.Remove(userId);
            }

            if (config.AdminsBypass && iplayer.IsAdmin) return null;

            string lower = message.ToLowerInvariant();
            foreach (string word in config.BlockedWords)
            {
                if (string.IsNullOrEmpty(word)) continue;
                if (lower.Contains(word.ToLowerInvariant()))
                {
                    iplayer.Reply(Msg("Filtered", userId));
                    return false;
                }
            }
            return null;
        }

        [ChatCommand("mute")]
        private void CmdMute(BasePlayer player, string command, string[] args)
        {
            if (player == null || !permission.UserHasPermission(player.UserIDString, PermModerate)) return;
            if (args.Length < 1) { SendReply(player, "/mute <nameOrId> [minutes]"); return; }

            BasePlayer target = BasePlayer.FindAwakeOrSleeping(args[0]);
            if (target == null) return;

            int minutes = config.DefaultMuteMinutes;
            if (args.Length > 1) int.TryParse(args[1], out minutes);

            data.Mutes[target.UserIDString] = DateTime.UtcNow.AddMinutes(Mathf.Max(1, minutes));
            SendReply(player, Msg("MuteSet", player.UserIDString, target.displayName, minutes));
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        [ChatCommand("unmute")]
        private void CmdUnmute(BasePlayer player, string command, string[] args)
        {
            if (player == null || !permission.UserHasPermission(player.UserIDString, PermModerate)) return;
            if (args.Length < 1) return;

            BasePlayer target = BasePlayer.FindAwakeOrSleeping(args[0]);
            if (target == null) return;

            if (data.Mutes.Remove(target.UserIDString))
            {
                SendReply(player, Msg("MuteCleared", player.UserIDString, target.displayName));
                Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            }
            else SendReply(player, Msg("NotMuted", player.UserIDString, target.displayName));
        }

        private void Unload()
        {
            if (data != null) Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }
    }
}

The plugin keeps a set of muted player IDs and a configurable list of blocked words. OnUserChat lowercases the message, checks whether the sender is muted, and scans for any banned word. If either check hits, it sends the player a localized warning through SendReply and returns false to suppress the chat line for everyone else; mute expiry is tracked with a timestamp so no timer needs to be created. Admins skip the word filter, and the mute and unmute actions are chat commands gated by a permission. State is persisted to a data file in oxide/data and saved on unload.

Common use cases

  • Word filter

    Scan messages for banned terms and cancel the line while telling the sender why. Returning a non-null value stops the message from showing locally.

  • Temporary mutes

    Track muted player IDs with a timestamp and drop every chat line from them until the mute expires, without touching the game's own mute system.

  • Chat relay to Discord

    Combine with a relay plugin to forward messages, using the IPlayer's UserId and Name to build the webhook payload; return null so local chat still shows.

Pitfalls

  • OnPlayerChat wins the return value

    If any loaded plugin returns non-null from OnPlayerChat, Oxide ignores the OnUserChat return. When you need guaranteed suppression, use OnPlayerChat or make sure no other plugin blocks first.

  • Commands never arrive here

    Messages starting with the chat command prefix are dispatched to the command handler before this hook fires. Do not expect /command text in OnUserChat.

  • IPlayer is required

    Declaring BasePlayer instead of IPlayer makes Oxide skip your method because the argument is not that type. Keep the IPlayer parameter and cast when you need game members.

Build a plugin with OnUserChat

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

Prompt: Word filter using the OnUserChat 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 OnUserChat

Generate