Skip to content
RustPluginGenerator

OnPlayerChat

OnPlayerChat is called when a connected player sends a chat message that is not a chat command, after the game has escaped rich text and before the message is broadcast to anyone.

Signature
object OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel)

Parameters

Hook parameters
ParameterTypeDescription
playerBasePlayerThe connected BasePlayer who sent the message. Never null at this point: offline or disconnected senders are routed to OnPlayerOfflineChat instead.
messagestringThe chat text after rich text markup has been stripped with EscapeRichText. Chat command messages (starting with the command prefix) never reach this hook.
channelConVar.Chat.ChatChannelThe Chat.ChatChannel the message was sent on: Global, Team, Server, Cards or Local.

Return value

Returning null keeps the message flowing through the normal chat path and the result of OnUserChat is returned instead. Returning any non-null object (commonly true) makes IOnPlayerChat return that object immediately, which suppresses the default handling of that message.

How it works

Oxide fires the hook from RustCore.IOnPlayerChat, which is the entry point for every player chat line. Before the hook runs, the server drops empty messages and the literal text text, forwards anything with the chat command prefix to TryRunPlayerCommand and returns, and applies EscapeRichText to the message. Offline or disconnected senders go to OnPlayerOfflineChat instead, so player here is always a live, connected BasePlayer.

Return a non-null value to suppress the default chat handling for that message: the original method returns your value and the message is not shown to other players (and no OnUserChat result is used). Return null to let the message through normally. channel lets you treat Global, Team and Local lines differently. Because the hook fires for every chat line, keep the body cheap and avoid logging on the hot path. Note that rich text tags are already escaped when you receive message, so you cannot re-inject color codes by returning the same string.

Example plugin: Silence and flag chat messages containing blocked words

ChatWordFilter.cs
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("Chat Word Filter", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks chat messages containing configured words and alerts staff")]
    public class ChatWordFilter : RustPlugin
    {
        private const string PermNotify = "chatwordfilter.notify";

        private PluginConfig config;
        private Regex blockedPattern;

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

            [JsonProperty("Filter team chat")]
            public bool FilterTeamChat = true;
        }

        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>
            {
                ["Blocked"] = "Your message was hidden: it contains a blocked word.",
                ["StaffAlert"] = "{0} tried to send a filtered message: {1}"
            }, 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(PermNotify, this);
            BuildPattern();
        }

        private void BuildPattern()
        {
            var words = new List<string>();
            foreach (var word in config.BlockedWords)
            {
                if (!string.IsNullOrEmpty(word)) words.Add(Regex.Escape(word.ToLowerInvariant()));
            }
            blockedPattern = words.Count > 0 ? new Regex(@"\b(" + string.Join("|", words) + @")\b", RegexOptions.IgnoreCase) : null;
        }

        private object OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel)
        {
            if (player == null || string.IsNullOrEmpty(message) || blockedPattern == null) return null;
            if (!config.FilterTeamChat && channel == ConVar.Chat.ChatChannel.Team) return null;
            if (!blockedPattern.IsMatch(message)) return null;

            player.ChatMessage(Msg("Blocked", player.UserIDString));

            foreach (var target in BasePlayer.activePlayerList)
            {
                if (target == null || !target.IsConnected) continue;
                if (target.UserIDString == player.UserIDString) continue;
                if (permission.UserHasPermission(target.UserIDString, PermNotify))
                {
                    target.ChatMessage(Msg("StaffAlert", target.UserIDString, player.displayName, message));
                }
            }

            return true;
        }
    }
}

The plugin loads a list of blocked words from its config and lowercases them once on load. In OnPlayerChat it scans the incoming message for a whole-word match using a regex built from the escaped word list. When a match is found it sends a warning only to the sender through the language file, alerts staff with the chatwordfilter.notify permission, and returns true to stop the message from reaching anyone else. Unfiltered messages return null so normal chat continues. Matches against teammates on the Team channel are filtered the same way by default, and a config flag lets admins disable filtering for the Team channel if they use it for coordination.

Common use cases

  • Block banned words or links

    Return true when a message matches a blocked pattern so spam, advertisements or slurs never reach the chat buffer. Keep the word list in config so admins can edit it without recompiling.

  • Per-channel gating

    Use the channel argument to redirect or reject messages: for example deny Global chat for players without a permission while leaving Team chat untouched.

  • Audit trail for staff

    Log matched messages with the sender's name and Steam ID to a data file and notify online admins via permission, without altering normal chat.

Pitfalls

  • Commands never reach the hook

    Anything starting with the chat command prefix is consumed by the command handler before OnPlayerChat is called, so you cannot filter or intercept plugin commands here.

  • Rich text is already escaped

    The message passed in has been through EscapeRichText, so returning it unchanged cannot restore color codes and any tags the player typed are shown literally.

  • Offline senders are excluded

    If the player is disconnected the server calls OnPlayerOfflineChat instead. Handle that hook too if you need to cover messages from the Rust+ app or reconnect edge cases.

Plugin recipes that use OnPlayerChat

Build a plugin with OnPlayerChat

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

Prompt: Block banned words or links using the OnPlayerChat 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 OnPlayerChat

Generate