Skip to content
RustPluginGenerator

How to make a Rust command alias plugin (Oxide chat shortcuts)

This plugin maps short chat commands like /h and /k to longer ones such as /home and /kit, defined entirely in its config file, using the OnPlayerCommand hook to intercept and reroute the alias.

Players type the same long commands dozens of times per session, and a typo in /teleport accept costs them a second attempt. Server owners usually solve this by asking players to remember more commands, which never works. The cleaner approach is to define aliases in the plugin config so /h behaves exactly like /home, whether the target is another plugin's command or a vanilla one.

The plugin below keeps a dictionary of alias to target in oxide/config/CommandAliases.json. When a player sends a chat command, the OnPlayerCommand hook fires before the command is dispatched. The plugin looks the first token up in the config, rebuilds the argu

The plugin code

CommandAliases.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Command Aliases", "RustPluginGenerator", "1.0.0")]
    [Description("Maps short chat commands such as /h to longer ones like /home using config-defined aliases.")]
    public class CommandAliases : RustPlugin
    {
        private const string PermAdmin = "commandaliases.admin";

        private PluginConfig config;
        private readonly Dictionary<ulong, float> lastAliasAt = new Dictionary<ulong, float>();

        private class PluginConfig
        {
            [JsonProperty("Alias map (alias -> target command)")]
            public Dictionary<string, string> Aliases { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                ["h"] = "home",
                ["k"] = "kit",
                ["t"] = "trade"
            };

            [JsonProperty("Seconds between alias uses per player")]
            public float CooldownSeconds { get; set; } = 0.5f;
        }

        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>
            {
                ["AliasesReloaded"] = "Reloaded {0} command aliases.",
                ["AliasesList"] = "{0} aliases: {1}",
                ["AliasesNone"] = "No aliases are configured.",
                ["NoPermission"] = "You do not have permission to use that 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(PermAdmin, this);
        }

        private void OnServerInitialized()
        {
            if (config.Aliases == null)
            {
                config.Aliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            }
        }

        private object OnPlayerCommand(BasePlayer player, string cmd, string[] args)
        {
            if (player == null || player.IsNpc || string.IsNullOrEmpty(cmd) || config?.Aliases == null) return null;
            if (config.Aliases.Count == 0) return null;
            if (cmd.StartsWith("chat.", StringComparison.OrdinalIgnoreCase)) return null;
            if (player.IsDead() || player.IsSleeping() || !player.IsConnected) return null;

            string target;
            if (!config.Aliases.TryGetValue(cmd, out target) || string.IsNullOrEmpty(target)) return null;

            string targetCommand = target.Trim().TrimStart('/');
            string firstWord = targetCommand.Split(' ')[0];
            if (string.Equals(firstWord, cmd, StringComparison.OrdinalIgnoreCase)) return null;

            float now = Time.realtimeSinceStartup;
            float last;
            if (lastAliasAt.TryGetValue(player.userID, out last) && now - last < config.CooldownSeconds)
            {
                return null;
            }
            lastAliasAt[player.userID] = now;

            string fullCommand = "/" + targetCommand;
            if (args != null && args.Length > 0)
            {
                fullCommand += " " + string.Join(" ", args);
            }

            player.SendConsoleCommand("chat.say", fullCommand);
            return null;
        }

        private void OnPlayerDisconnected(BasePlayer player)
        {
            if (player != null) lastAliasAt.Remove(player.userID);
        }

        [ConsoleCommand("alias.reload")]
        private void CmdReload(ConsoleSystem.Arg arg)
        {
            if (!CanUseAdmin(arg)) return;
            LoadConfig();
            arg.ReplyWith(Msg("AliasesReloaded", null, config.Aliases?.Count ?? 0));
        }

        [ConsoleCommand("alias.list")]
        private void CmdList(ConsoleSystem.Arg arg)
        {
            if (!CanUseAdmin(arg)) return;
            if (config.Aliases == null || config.Aliases.Count == 0)
            {
                arg.ReplyWith(Msg("AliasesNone"));
                return;
            }
            var parts = new List<string>();
            foreach (var pair in config.Aliases)
            {
                parts.Add("/" + pair.Key + " -> /" + pair.Value);
            }
            arg.ReplyWith(Msg("AliasesList", null, config.Aliases.Count, string.Join(", ", parts)));
        }

        private bool CanUseAdmin(ConsoleSystem.Arg arg)
        {
            if (arg == null) return false;
            var player = arg.Player();
            if (player == null) return true;
            if (permission.UserHasPermission(player.UserIDString, PermAdmin)) return true;
            arg.ReplyWith(Msg("NoPermission", player.UserIDString));
            return false;
        }

        private void Unload()
        {
            lastAliasAt.Clear();
        }
    }
}

Hooks used: OnServerInitialized, OnPlayerCommand, OnPlayerDisconnected

Config file

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

CommandAliases.json
{
  "Alias map (alias -> target command)": {
    "h": "home",
    "k": "kit",
    "t": "trade"
  },
  "Seconds between alias uses per player": 0.5
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
alias.listPrints every configured alias and its target to the server console.commandaliases.admin
alias.reloadReloads the config file and reports how many aliases are now active.commandaliases.admin
Permissions
PermissionGrants
commandaliases.adminGrants access to the alias.list and alias.reload console commands.

How the code works

  1. Hook the incoming chat command

    OnPlayerCommand(BasePlayer player, string cmd, string[] args) fires for every chat command a player sends, with cmd holding the command name without the leading slash and args the remaining tokens. The method returns an object: returning null lets the command continue normally. The plugin null-checks the player, the command string and the args array before doing anything else.

  2. Resolve the alias from config

    The Aliases config key is a dictionary of alias to target, both stored without the slash. Values may include arguments such as kit starter, so the plugin splits the target on spaces to separate the command name from its fixed arguments. Lookup uses StringComparer.OrdinalIgnoreCase so /H and /h behave the same, and the original args are appended after any arguments baked into the target.

  3. Run the target command

    Once the alias resolves, the plugin rebuilds the full command line and sends it back to the server with player.SendConsoleCommand("chat.say", fullCommand). That routes it through the normal chat parser, so permissions, cooldowns and argument handling of the target plugin all apply exactly as if the player had typed it. Returning null afterwards keeps the original input flowing to any other OnPlayerCommand subscribers.

  4. Stop self-referencing loops

    A config entry like "h": "h" or a chain of two aliases pointing at each other would bounce forever. Before executing, the plugin compares the resolved target with the original command; if they match it logs nothing and returns. Chained aliases are deliberately not resolved recursively, which keeps behaviour predictable and avoids loops entirely.

  5. Throttle and guard the redirect

    Because chat.say is a player input path, the plugin keeps a small per-player timestamp check: the same alias cannot fire more than once every 0.5 seconds. This prevents a spammed keybind from flooding the target command. Players who are dead, sleeping or disconnected are skipped, and the redirect is ignored for commands starting with chat. to avoid recursive re-entry.

  6. Admin console commands and permissions

    alias.reload reloads oxide/config/CommandAliases.json at runtime with LoadConfig() so admins do not need to reload the whole plugin, and alias.list dumps the current map to the console. Both are registered with [ConsoleCommand] and check permission.UserHasPermission using the commandaliases.admin permission, which is registered in Init and granted to the admin group on first load.

  7. Ship sensible defaults

    LoadDefaultConfig seeds a small starter set: h to home, k to kit, t to trade, and s to s plus nothing else, so an admin who wants no defaults just clears the dictionary. Messages for the console commands live in LoadDefaultMessages and are fetched through the Msg helper, so they can be localized later without touching code.

Install it on your server

  1. Save CommandAliases.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/CommandAliases.json (Carbon: carbon/configs/CommandAliases.json). After editing, reload it:oxide.reload CommandAliases c.reload CommandAliases # Carbon
  4. Grant the permission

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

Variations

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

  • Alias usage counter

    Track how often each alias is used so admins can see which shortcuts are popular.

    Generate this version
  • Alias with forced arguments

    Support targets that require arguments, e.g. /tc -> teamchat with a fixed word.

    Generate this version
  • Aliased commands with permission gating

    Restrict specific aliases to players holding an Oxide permission.

    Generate this version

FAQ

Will does this work with other plugins' commands?

Yes. The redirect re-sends the command through the chat parser with player.SendConsoleCommand("chat.say", ...), so any command registered by another Oxide plugin or by Rust itself is handled by its normal handler, including its own permission and cooldown checks.

Does it work on Carbon?

Yes. Carbon loads Oxide plugins and calls the OnPlayerCommand hook with the same signature. Drop the .cs file into carbon/plugins, reload, and the config appears under carbon/config/CommandAliases.json.

Can an alias have arguments baked in?

Yes. A config value like kit starter makes /k send /kit starter. Any arguments the player typed after the alias are appended after the fixed ones, so /k vip becomes /kit starter vip.

What happens if the target command does not exist?

Rust simply reports an unknown command to the player in the usual way; the plugin does not validate targets, because plugin-provided commands are registered at runtime and cannot be checked reliably at load time.

Can aliases collide with real commands?

Avoid defining an alias whose name matches a real command. The hook fires before dispatch and cannot tell which command the player meant, so the redirect always wins. Use the alias.list console command to review the current map after editing config.

Customize it for your server

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

Prompt: Build a Rust Oxide plugin that reads an alias-to-command map from config and, in the OnPlayerCommand hook, re-sends the target chat command for the player, with defaults h=home and k=kit.
Customize for my server

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

Last updated Oct 26, 2026.

Customize command aliases

Customize