How to make a Rust join and leave messages plugin (Oxide/Carbon)
Broadcasts a configurable chat message when a player connects to the server and when a player disconnects, including the disconnect reason. Uses the OnPlayerConnected and OnPlayerDisconnected hooks and hides players with a permission from announcements.
By default a Rust server does not announce when someone joins or leaves. When a queue drains or a player crashes, nobody in global chat knows it happened. This plugin listens to OnPlayerConnected and OnPlayerDisconnected, formats a line from the templates registered in the language file, and sends it to every connected player. The leave line can include whichever reason string Rust reports. Two config switches let you mute join and leave messages independently. A permission hides staff accounts so admin logins do not spam the broadcast, and a /jlm chat command lets admins reload the config or flip the switches in game. Messages carry chat tags defined in the language file, which can be edited without recompiling.
The plugin code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Join Leave Messages", "RustPluginGenerator", "1.0.0")]
[Description("Broadcasts configurable join and leave messages, optionally including the disconnect reason.")]
public class JoinLeaveMessages : RustPlugin
{
private const string PermHidden = "joinleavemessages.hidden";
private const string PermAdmin = "joinleavemessages.admin";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Announce joins")]
public bool AnnounceJoins { get; set; } = true;
[JsonProperty("Announce leaves")]
public bool AnnounceLeaves { get; set; } = true;
[JsonProperty("Show disconnect reason")]
public bool ShowReason { get; set; } = true;
[JsonProperty("Write events to server console")]
public bool ConsoleLog { get; set; } = true;
[JsonProperty("Console join line (0 = name)")]
public string ConsoleJoin { get; set; } = "{0} connected";
[JsonProperty("Console leave line (0 = name, 1 = reason)")]
public string ConsoleLeave { get; set; } = "{0} disconnected ({1})";
}
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>
{
["Join"] = "<color=#88cc88>{0}</color> joined the server",
["Leave"] = "<color=#cc8888>{0}</color> left the server",
["LeaveReason"] = "<color=#cc8888>{0}</color> left the server ({1})",
["UnknownReason"] = "unknown",
["JoinOn"] = "Join announcements are now enabled",
["JoinOff"] = "Join announcements are now disabled",
["LeaveOn"] = "Leave announcements are now enabled",
["LeaveOff"] = "Leave announcements are now disabled",
["Reloaded"] = "Configuration reloaded",
["NoPermission"] = "You do not have permission to use this command",
["Usage"] = "Usage: /jlm reload | /jlm toggle join | /jlm toggle leave"
}, this);
}
private string Msg(string key, string userId = null, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
private void Init()
{
permission.RegisterPermission(PermHidden, this);
permission.RegisterPermission(PermAdmin, this);
}
private bool IsHidden(BasePlayer player)
{
return permission.UserHasPermission(player.UserIDString, PermHidden);
}
private void OnPlayerConnected(BasePlayer player)
{
if (player == null || !config.AnnounceJoins) return;
if (IsHidden(player)) return;
string name = player.displayName ?? "Unknown";
Server.Broadcast(Msg("Join", null, name));
if (config.ConsoleLog)
{
Puts(string.Format(config.ConsoleJoin ?? "{0} connected", name));
}
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null || !config.AnnounceLeaves) return;
if (IsHidden(player)) return;
string name = player.displayName ?? "Unknown";
string safeReason = string.IsNullOrEmpty(reason) ? Msg("UnknownReason") : reason;
// Strip anything that could break the chat line
safeReason = safeReason.Replace("<", string.Empty).Replace(">", string.Empty);
if (config.ShowReason)
{
Server.Broadcast(Msg("LeaveReason", null, name, safeReason));
}
else
{
Server.Broadcast(Msg("Leave", null, name));
}
if (config.ConsoleLog)
{
Puts(string.Format(config.ConsoleLeave ?? "{0} disconnected ({1})", name, safeReason));
}
}
[ChatCommand("jlm")]
private void CmdJlm(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
if (args == null || args.Length == 0)
{
SendReply(player, Msg("Usage", player.UserIDString));
return;
}
switch (args[0].ToLower())
{
case "reload":
LoadConfig();
LoadDefaultMessages();
SendReply(player, Msg("Reloaded", player.UserIDString));
return;
case "toggle":
if (args.Length < 2)
{
SendReply(player, Msg("Usage", player.UserIDString));
return;
}
if (args[1].Equals("join", StringComparison.OrdinalIgnoreCase))
{
config.AnnounceJoins = !config.AnnounceJoins;
SaveConfig();
SendReply(player, Msg(config.AnnounceJoins ? "JoinOn" : "JoinOff", player.UserIDString));
return;
}
if (args[1].Equals("leave", StringComparison.OrdinalIgnoreCase))
{
config.AnnounceLeaves = !config.AnnounceLeaves;
SaveConfig();
SendReply(player, Msg(config.AnnounceLeaves ? "LeaveOn" : "LeaveOff", player.UserIDString));
return;
}
SendReply(player, Msg("Usage", player.UserIDString));
return;
default:
SendReply(player, Msg("Usage", player.UserIDString));
return;
}
}
}
}Hooks used: OnPlayerConnected, OnPlayerDisconnected
Config file
Created automatically on first load at oxide/config/JoinLeaveMessages.json.
{
"Announce joins": true,
"Announce leaves": true,
"Show disconnect reason": true,
"Write events to server console": true,
"Console join line (0 = name)": "{0} connected",
"Console leave line (0 = name, 1 = reason)": "{0} disconnected ({1})"
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/jlm reload | Reload the config file and re-register the language messages. | joinleavemessages.admin |
/jlm toggle join | Turn join announcements on or off. | joinleavemessages.admin |
/jlm toggle leave | Turn leave announcements on or off. | joinleavemessages.admin |
| Permission | Grants |
|---|---|
joinleavemessages.hidden | Players with this permission are never announced when they join or leave. |
joinleavemessages.admin | Grants access to the /jlm chat commands. |
How the code works
Hooks and parameter types
OnPlayerConnected(BasePlayer player)fires when a player is accepted by the server.OnPlayerDisconnected(BasePlayer player, string reason)fires while the player is being removed, sodisplayNameandUserIDStringare still readable inside it. Both hooks declareBasePlayer, so Oxide calls them for every connection event. The first check in each method is a null test, because a disconnect during authentication can deliver a partial player object.Message templates and the language file
LoadDefaultMessagesregisters the keysJoin,LeaveandLeaveReasonwith{0}and{1}placeholders, plus the command replies.Msgcallsstring.Formatonlang.GetMessageand is the only way text reaches a player. The default strings contain chat colour tags, which is why the result goes straight toServer.Broadcast. The config carries separateConsole join lineandConsole leave linetemplates used only for thePutslog line.Hiding admins
IsHidden(player)checkspermission.UserHasPermission(player.UserIDString, PermHidden). Grant thejoinleavemessages.hiddenpermission to staff accounts that should not be announced; the hidden player still sees other players' join and leave lines, only their own are skipped. Bothjoinleavemessages.hiddenandjoinleavemessages.adminare registered inInit, so the permission is available as soon as the plugin loads.Disconnect reasons
The
reasonargument arrives as a plain string. If it is null or empty the plugin substitutes theUnknownReasonlang entry, so the line never ends with a dangling bracket. Angle brackets are stripped from the reason before formatting, which stops a crafted reason from opening a chat tag that would swallow the rest of the message. TheShow disconnect reasonconfig key picks between theLeaveReasonandLeavetemplates.Config switches and the /jlm command
PluginConfigholdsAnnounce joins,Announce leaves,Show disconnect reason,Write events to server consoleand the two console line templates. The[ChatCommand("jlm")]handler requiresjoinleavemessages.admin, answersreloadby callingLoadConfigandLoadDefaultMessages, and handlestoggle joinandtoggle leaveby flipping a bool, callingSaveConfigand replying with the matching lang key.Broadcasting and console logging
Each hook returns early when its config switch is off or the player is hidden, so nothing is sent on a disabled channel.
Server.Broadcastthen delivers the formatted line to every connected player. ThePutscall is gated behindWrite events to server consoleand only runs on connection events, never on a per-tick hook. No timers, UI or data files are used, so there is nothing to tear down on unload.
Install it on your server
Save JoinLeaveMessages.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/JoinLeaveMessages.json(Carbon:carbon/configs/JoinLeaveMessages.json). After editing, reload it:oxide.reload JoinLeaveMessages c.reload JoinLeaveMessages # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default joinleavemessages.hidden c.grant group default joinleavemessages.hidden # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Count online players in the message
Append the current online player count to every join and leave line.
Generate this versionWelcome message with server rules
Send a longer welcome block to the joining player only.
Generate this versionDiscord webhook for joins
Post connection events to a webhook URL from a companion plugin reference.
Generate this version
FAQ
Why do leave messages sometimes appear out of order?
OnPlayerDisconnected fires during removal and the line is queued through the server's chat broadcast. If several players leave at once you can see reordered lines. The plugin reads nothing that requires the entity to still exist, so the message content itself is always correct.
How do I stop my admin account from being announced?
Give the account the joinleavemessages.hidden permission through your usual permission management. The permission is registered in Init, so it works without a reload. Hidden players still see other players' join and leave messages; only their own lines are suppressed.
Can I change the colours of the messages?
Yes. Edit the Join, Leave and LeaveReason entries in the language file and change the colour tags around the text, then reload the plugin. The chat text comes from the language file; the console line templates in the config only affect what Puts writes to the server log.
Will this work on Carbon?
Yes. Carbon loads the same Oxide plugin file. Drop JoinLeaveMessages.cs into the plugins folder and the hooks and permission calls behave identically. Config and language files live under the same oxide/config and oxide/lang paths.
Does it announce players who reconnect after a crash?
It does, because every accepted connection fires OnPlayerConnected. If you only want to announce first-time visits, you would need to store seen Steam IDs in a data file; the plugin above does not do that.
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 join and leave messages
Customize