Skip to content
RustPluginGenerator

How to make a Rust name filter plugin (kick on join)

Blocks connections when a player's display name contains a blocked word, looks like advertising, or is shorter than a minimum length. Uses the OnPlayerConnected hook to kick the player with a configurable message.

Rust does not police display names. A player can join with a name containing a URL, a Discord tag, a slur, or a single character, and the only feedback you get is the chat box. The practical fix is to validate the name the moment the connection is accepted and remove the player before they interact with anything.

This plugin runs in OnPlayerConnected, which fires server-side after the player object exists but before normal gameplay starts. It lowercases the name once, then checks three independent rules: length against Minimum Name Length, substrings against Blocked Words, and regular expressions against Blocked Patterns (the defaults cover URLs and invite-style domains). Matching is done on the lowercased name and regexes are compiled once at load, so the hook stays cheap. A matching player gets Kick(Banned Name Kick Message), and admins with the bypass permission are skipped. Every refusal is written to the server console with the Steam ID and the offending name so you can review it later.

The plugin code

NameFilter.cs
using System.Linq;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Name Filter", "RustPluginGenerator", "1.0.0")]
    [Description("Kicks connecting players whose display name is too short, contains blocked words, or looks like advertising.")]
    public class NameFilter : RustPlugin
    {
        private const string PermBypass = "namefilter.bypass";
        private const string PermAdmin = "namefilter.admin";

        private PluginConfig config;
        private readonly List<Regex> patterns = new List<Regex>();

        private class PluginConfig
        {
            [JsonProperty("Minimum Name Length")]
            public int MinimumNameLength = 3;

            [JsonProperty("Blocked Words")]
            public string[] BlockedWords =
            {
                "admin",
                "owner",
                "moderator",
                "server",
                "vac_ban",
                "freevip"
            };

            [JsonProperty("Blocked Patterns")]
            public string[] BlockedPatterns =
            {
                @"https?://",
                @"www\.",
                @"discord\.gg",
                @"\.com\b",
                @"\.gg\b",
                @"\b(?:discord|steam)\b.*#\d{4}",
                @"(?:discord|steam|skype|telegram)[\.\-_]"
            };

            [JsonProperty("Kick Message")]
            public string KickMessage = "Your display name is not allowed on this server. Please change it and reconnect.";

            [JsonProperty("Log Blocked Connections")]
            public bool LogBlockedConnections = 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();
            }

            CompilePatterns();
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["CheckClean"] = "{0} ({1}) passes the name filter.",
                ["CheckBlocked"] = "{0} ({1}) is blocked: {2}",
                ["TargetNotFound"] = "No player found for '{0}'.",
                ["Reloaded"] = "NameFilter configuration reloaded."
            }, this);
        }

        private void Init()
        {
            permission.RegisterPermission(PermBypass, this);
            permission.RegisterPermission(PermAdmin, this);
        }

        private void CompilePatterns()
        {
            patterns.Clear();
            if (config.BlockedPatterns == null) return;

            foreach (string pattern in config.BlockedPatterns)
            {
                if (string.IsNullOrEmpty(pattern)) continue;
                try
                {
                    patterns.Add(new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant));
                }
                catch (Exception ex)
                {
                    PrintWarning("Invalid pattern '" + pattern + "': " + ex.Message);
                }
            }
        }

        private void OnPlayerConnected(BasePlayer player)
        {
            if (player == null || player.IsNpc) return;
            if (permission.UserHasPermission(player.UserIDString, PermBypass)) return;

            string name = player.displayName ?? string.Empty;
            string reason;
            if (!IsNameBlocked(name, out reason)) return;

            if (config.LogBlockedConnections)
                Puts("Blocked connection: " + name + " (" + player.UserIDString + ") - " + reason);

            player.Kick(config.KickMessage);
        }

        private bool IsNameBlocked(string name, out string reason)
        {
            reason = null;

            if (name.Length < config.MinimumNameLength)
            {
                reason = "name shorter than " + config.MinimumNameLength + " characters";
                return true;
            }

            string lower = name.ToLowerInvariant();

            if (config.BlockedWords != null)
            {
                foreach (string word in config.BlockedWords)
                {
                    if (string.IsNullOrEmpty(word)) continue;
                    if (lower.Contains(word.ToLowerInvariant()))
                    {
                        reason = "contains blocked word '" + word + "'";
                        return true;
                    }
                }
            }

            foreach (Regex regex in patterns)
            {
                if (regex.IsMatch(name))
                {
                    reason = "matches pattern '" + regex + "'";
                    return true;
                }
            }

            return false;
        }

        private string Msg(string key, string userId = null, params object[] args)
        {
            return string.Format(lang.GetMessage(key, this, userId), args);
        }

        [ConsoleCommand("namefilter.check")]
        private void CmdCheck(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin && arg.Player() != null && !permission.UserHasPermission(arg.Player().UserIDString, PermAdmin))
            {
                arg.ReplyWith("You do not have permission to use this command.");
                return;
            }

            string target = arg.GetString(0, string.Empty);
            if (string.IsNullOrEmpty(target))
            {
                arg.ReplyWith("Usage: namefilter.check <player name or steamid>");
                return;
            }

            var player = BasePlayer.FindAwakeOrSleeping(target);
            if (player == null)
            {
                arg.ReplyWith(Msg("TargetNotFound", null, target));
                return;
            }

            string reason;
            if (IsNameBlocked(player.displayName ?? string.Empty, out reason))
                arg.ReplyWith(Msg("CheckBlocked", null, player.displayName, player.UserIDString, reason));
            else
                arg.ReplyWith(Msg("CheckClean", null, player.displayName, player.UserIDString));
        }

        [ConsoleCommand("namefilter.reload")]
        private void CmdReload(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin && arg.Player() != null && !permission.UserHasPermission(arg.Player().UserIDString, PermAdmin))
            {
                arg.ReplyWith("You do not have permission to use this command.");
                return;
            }

            LoadConfig();
            arg.ReplyWith(Msg("Reloaded"));
        }
    }
}

Hooks used: OnPlayerConnected

Config file

Created automatically on first load at oxide/config/NameFilter.json.

NameFilter.json
{
  "Minimum Name Length": 3,
  "Blocked Words": [
    "admin",
    "owner",
    "moderator",
    "server",
    "vac_ban",
    "freevip"
  ],
  "Blocked Patterns": [
    "https?://",
    "www\\.",
    "discord\\.gg",
    "\\.com\\b",
    "\\.gg\\b",
    "\\b(?:discord|steam)\\b.*#\\d{4}",
    "(?:discord|steam|skype|telegram)[\\.\\-_]"
  ],
  "Kick Message": "Your display name is not allowed on this server. Please change it and reconnect.",
  "Log Blocked Connections": true
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
namefilter.check <player name or steamid>Runs the configured name rules against a player's current display name and reports the result to console.namefilter.admin
namefilter.reloadReloads the plugin configuration from oxide/config/NameFilter.json without restarting the plugin.namefilter.admin
Permissions
PermissionGrants
namefilter.bypassSkips the name filter for this player or group.
namefilter.adminAllows use of the namefilter.check and namefilter.reload console commands.

How the code works

  1. Hook the connection and validate early

    OnPlayerConnected(BasePlayer player) fires once per accepted connection. Null-check the player, then bail out if player.IsNpc or the player has namefilter.bypass. Everything else goes through IsNameBlocked(string name, out string reason), which returns true for the first failing rule. When it returns true, call player.Kick(config.KickMessage) and log the Steam ID, name and reason with Puts. Because the kick happens here, the player never reaches spawn.

  2. Length rule

    Minimum Name Length (default 3) uses name.Length on the raw display name, not the lowercased copy, so trailing whitespace still counts. Set it to 1 to disable the rule in practice, since Rust already forbids empty names. The length check runs first because it is the cheapest test; blocking a two-character name should not spend time on a regex scan.

  3. Blocked word list

    Blocked Words is a string array in the config. At load the plugin lowercases the name once (name.ToLowerInvariant()) and compares with Contains against each entry, which is also lowercased when loading. Uppercase staff impersonation is covered without listing both cases. Keep the list focused on unambiguous substrings; short entries like ad will match legitimate names such as Adam, so use the pattern list for anything that needs context.

  4. Advertising and URL patterns

    Blocked Patterns holds .NET regex strings. The plugin builds a List<Regex> in LoadConfig with RegexOptions.IgnoreCase | RegexOptions.Compiled, and a broken expression is caught, warned about with PrintWarning and skipped rather than crashing the load. The defaults catch http://, www., discord.gg, .com/.gg style domains and discord tag syntax with a four-digit discriminator. Matching a .gg domain on the bare suffix means names ending in .gg are blocked too.

  5. Kick message and logging

    KickMessage is a single string in the config, defaulting to a short explanation with the server restart advice. player.Kick(string) takes the reason shown in the client disconnect dialog, so the text should tell the player what to change. Console output uses Puts only inside the connect hook, which is rare enough to be safe. Nothing is sent to chat, so a kicked player never sees a chat line.

  6. Admin commands and reload

    namefilter.check resolves the target with BasePlayer.FindAwakeOrSleeping, then runs the same IsNameBlocked path and prints the reason via arg.ReplyWith. namefilter.reload calls the base LoadConfig and recompiles the regex list, so you can edit the JSON and apply it without unloading the plugin. Both are console commands gated on namefilter.admin; arg.Player() can be null under RCON. Player-facing replies go through the language file via the Msg helper.

Install it on your server

  1. Save NameFilter.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. 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.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/NameFilter.json (Carbon: carbon/configs/NameFilter.json). After editing, reload it:oxide.reload NameFilter c.reload NameFilter # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default namefilter.bypass c.grant group default namefilter.bypass # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

FAQ

Does OnPlayerConnected fire before or after the player spawns?

It fires after the connection is accepted and the player object exists, but before normal spawning and gameplay. Kicking there removes the player from the server cleanly; they see the disconnect dialog with your KickMessage and are not left as a ghost entity.

Will this catch names that are only whitespace or zero-width characters?

The length rule counts every character, so zero-width characters can pad a name past the minimum. If that matters to you, add a regex such as \p{C} to Blocked Patterns; .NET regex supports Unicode categories, and the compiled list runs on every connection.

Can admins with the bypass permission still join with a bad name?

Yes. namefilter.bypass is checked at the top of the hook, so staff and moderators can keep whatever name they use. The connect is still logged only when a rule would have matched and the player was skipped, which helps you confirm the permission is granting what you expect.

How do I install it on Oxide or Carbon?

Drop NameFilter.cs into oxide/plugins, let the server compile it, then edit oxide/config/NameFilter.json and run namefilter.reload in the console. Carbon loads the same file from its plugins directory; no changes are needed. Grant namefilter.admin to your staff group with oxide.grant.

Does the filter apply to renamed players?

No. Only OnPlayerConnected is handled, so a player who changes their name mid-session is not re-checked. Rust name changes usually require reconnecting, at which point the filter runs again; if you need per-change enforcement, check the name in OnPlayerChat or a periodic timer instead.

Customize it for your server

Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.

Prompt: Create a Rust Oxide plugin called NameFilter that kicks players on connect when their name is shorter than 3 characters, contains a blocked word, or matches a blocked URL or Discord advertising regex, with a configurable kick message.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize player name filter

Customize