How to make a Rust help command plugin (paged /help list from config)
A paged /help command that lists server commands and short descriptions stored in the plugin config, 8 entries per page, with /help 2 for the next page. It registers a chat command in Init and needs no game hooks.
Players join a Rust server and have no idea which chat commands you run. Vanilla Rust has no central command list, and hard-coding one into every plugin gets stale fast. This plugin keeps the whole list in oxide/config/HelpCommand.json, so you can add or remove entries without recompiling anything. /help prints the first eight entries in chat, /help 2 prints the second eight, and so on; entries beyond the page count are never lost, and one config key lets you set a footer line shown on every page. The README-style header message, page number, and the "no page there" feedback all go through the language file, so non-English servers can translate them. The implementation only uses Oxide's permission, lang, chat-command and config APIs; there is nothing to patch, no hook, and no per-tick cost.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Help Command", "RustPluginGenerator", "1.0.0")]
[Description("Shows a paged list of server commands and descriptions stored in the config.")]
public class HelpCommand : RustPlugin
{
private const int PageSize = 8;
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Header (use {0} for page, {1} for total pages)")]
public string Header = "Server commands - page {0}/{1}";
[JsonProperty("Footer")]
public string Footer = "Type /help <page> for more.";
[JsonProperty("Commands")]
public List<CommandEntry> Commands = new List<CommandEntry>
{
new CommandEntry { Command = "/kit", Description = "Claim your starter kit", ShowInHelp = true },
new CommandEntry { Command = "/trade <player>", Description = "Send a trade request", ShowInHelp = true },
new CommandEntry { Command = "/tp", Description = "Teleport request", ShowInHelp = true },
new CommandEntry { Command = "/report <player> <reason>", Description = "Report a player", ShowInHelp = true }
};
}
private class CommandEntry
{
[JsonProperty("Command")]
public string Command = "/command";
[JsonProperty("Description")]
public string Description = "What it does";
[JsonProperty("Show in help")]
public bool ShowInHelp = true;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null || config.Commands == 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>
{
["Header"] = "Server commands - page {0}/{1}",
["Line"] = "{0} - {1}",
["Footer"] = "Type /help <page> for more.",
["NoSuchPage"] = "There is no page {0}. Use /help 1.",
["NoCommands"] = "No commands are listed right now.",
["WrongUsage"] = "Usage: /help or /help <page>"
}, this);
}
private void Init()
{
cmd.AddChatCommand("help", this, nameof(CmdHelp));
}
private void CmdHelp(BasePlayer player, string command, string[] args)
{
if (player == null || config == null || config.Commands == null)
return;
var visible = new List<CommandEntry>();
foreach (var entry in config.Commands)
{
if (entry != null && entry.ShowInHelp && !string.IsNullOrEmpty(entry.Command))
visible.Add(entry);
}
if (visible.Count == 0)
{
SendReply(player, Msg("NoCommands", player.UserIDString));
return;
}
int page = 1;
if (args != null && args.Length > 0)
{
if (!int.TryParse(args[0], out page))
page = 1;
}
if (page < 1)
page = 1;
int pageCount = (int)Math.Ceiling(visible.Count / (double)PageSize);
if (page > pageCount)
{
SendReply(player, Msg("NoSuchPage", player.UserIDString, page));
return;
}
SendReply(player, Msg("Header", player.UserIDString, page, pageCount));
int start = (page - 1) * PageSize;
int end = Math.Min(start + PageSize, visible.Count);
for (int i = start; i < end; i++)
{
var entry = visible[i];
SendReply(player, Msg("Line", player.UserIDString, entry.Command, entry.Description));
}
if (!string.IsNullOrEmpty(config.Footer))
SendReply(player, Msg("Footer", player.UserIDString));
}
private string Msg(string key, string userId = null, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
}
}Config file
Created automatically on first load at oxide/config/HelpCommand.json.
{
"Header (use {0} for page, {1} for total pages)": "Server commands - page {0}/{1}",
"Footer": "Type /help <page> for more.",
"Commands": [
{
"Command": "/kit",
"Description": "Claim your starter kit",
"Show in help": true
},
{
"Command": "/trade <player>",
"Description": "Send a trade request",
"Show in help": true
},
{
"Command": "/tp",
"Description": "Teleport request",
"Show in help": true
},
{
"Command": "/report <player> <reason>",
"Description": "Report a player",
"Show in help": true
}
]
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/help | Shows page 1 of the command list (8 entries). | Everyone |
/help <page> | Shows the given page number, e.g. /help 2 for entries 9 to 16. | Everyone |
How the code works
Store the command list in the config
The nested
PluginConfigclass holds aList<CommandEntry>. EachCommandEntryhasCommand(the text to display, e.g./kit),Description(shown on the same line) andShowInHelp(a quick way to park an entry without deleting it). TheHeaderandFooterstrings wrap every page.LoadDefaultConfigseeds the list with a couple of realistic entries, matching the default config file, so the plugin works out of the box.Register the chat command
Initis the only startup hook used here. It callscmd.AddChatCommandwith the namehelp, so the command name can be changed without touching the attribute. The callback receives the player, the raw command string and thestring[] args. The method returns early whenplayeris null, so a stale command from a disconnected player cannot throw. No permission is registered, every connected player can run it.Resolve the requested page
The handler counts the visible entries first, then builds
pageCountwithMath.Ceiling(count / (double)PageSize), wherePageSizeis aconst intof 8. A missing argument means page 1.int.TryParseguards against/help abc, which falls back to page 1 instead of failing. The value is clamped to at least 1. If the requested page exceeds the page count you send theNoSuchPagemessage through theMsghelper and stop.Print only the entries for that page
The header is sent with
{0}as the current page and{1}as the total. The loop starts at(page - 1) * PageSizeand stops atstart + PageSizeor the end of the filtered list. Each entry is printed as one chat line built fromCommandandDescription. Entries withShowInHelpset to false are skipped before paging, so they do not create empty slots. The footer is appended last, also through the language file.Keep player-facing text in lang
LoadDefaultMessagesregistersHeader,Line,Footer,NoSuchPage,NoCommandsandWrongUsagein English.Msgformats them withlang.GetMessage(key, this, player.UserIDString)andstring.Format. Every one of those messages can be overridden inoxide/lang/en/HelpCommand.json; you do not need to edit the plugin to reword them.Config load and save
LoadConfigcallsbase.LoadConfig, triesConfig.ReadObject<PluginConfig>()and, on any exception or a null result, prints a warning and callsLoadDefaultConfig.SaveConfigwrites the object back. That means an admin who deletes a key or breaks the JSON gets a working default file the next time the plugin loads, rather than a crash. There is no data file and no timer, soUnloadneeds no cleanup.
Install it on your server
Save HelpCommand.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/HelpCommand.json(Carbon:carbon/configs/HelpCommand.json). After editing, reload it:oxide.reload HelpCommand c.reload HelpCommand # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Show the command list in a CUI panel
Render the page as a draggable Oxide UI panel with a Prev/Next button pair instead of chat lines.
Generate this versionSearch instead of paging
Let players type /help kit to filter entries by keyword across all pages.
Generate this versionPer-permission help
Hide entries the player cannot use by checking a permission string stored per entry.
Generate this version
FAQ
How do I add or remove commands from the list?
Edit oxide/config/HelpCommand.json, add an object to Commands with Command, Description and ShowInHelp, then reload the plugin. Set ShowInHelp to false to hide an entry without removing it. Pages recalculate automatically because the count is taken at runtime.
Why does /help collide with another plugin's command?
Oxide lets one plugin own a chat command name; the last plugin loaded registers it and the others log a conflict. Rename the command with cmd.AddChatCommand("commands", this, ...) in Init and update the Command strings in config if you also want them to match.
Can I translate the header and footer?
Yes. Edit oxide/lang/en/HelpCommand.json (or add a language folder for your locale) and change Header, Line, Footer, NoSuchPage, NoCommands and WrongUsage. The placeholders {0} and {1} in Header are the current and total page numbers.
How do I install it on Oxide or Carbon?
Drop HelpCommand.cs into oxide/plugins and let it compile, or upload it through the admin panel. Carbon loads the same file. Then edit oxide/config/HelpCommand.json and run oxide.reload HelpCommand. No permissions are registered and no hooks are patched.
Does this cost performance on a full server?
No. The plugin only does work when a player types /help; it has no OnTick, no timers and no entity subscriptions. Building one page is a single loop over at most eight list entries.
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 chat plugins
- Automatic announcementsAutoBroadcast sends a list of chat messages to every online player on a repeating timer, in order or random order, and fills {players} and {time}โฆOnServerInitialized
- Chat loggerRecords every chat message with timestamp, SteamID, name, channel and text into a capped oxide/data file, and adds a /chatlog command that prints aโฆOnServerInitializedOnPlayerChat
- Chat mentionsDetects @name tokens in chat and sends the mentioned online player a private notice plus a notification sound, with a per-sender cooldown.OnPlayerChatOnPlayerDisconnected
Last updated Oct 26, 2026.
Customize help command
Customize