How to make a Rust welcome panel plugin (CUI on spawn)
Shows a configurable CUI welcome panel with a title, message lines and server links when a player wakes up, closing on a button click or after a timeout. Uses OnPlayerSleepEnded and OnPlayerDisconnected.
New and returning players often land in the world without knowing the server rules, wipe schedule or Discord address. A chat broadcast works, but it scrolls away fast and players miss it. A CUI panel drawn at the moment the player finishes waking up is seen every session and can be dismissed with one click.
This plugin hooks OnPlayerSleepEnded, which fires when a player leaves the sleeping state after connecting or respawning, and builds a CuiElementContainer anchored to the screen centre. Title, body lines, a links block and the close button label all come from the config. The panel auto-closes after a configurable number of seconds through a tracked timer.Once, and pressing the close button destroys it immediately. A session set prevents the panel appearing twice in the same connection, and OnPlayerDisconnected clears that state so it shows again next time.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Welcome Panel", "RustPluginGenerator", "1.0.0")]
[Description("Shows a configurable CUI welcome panel when a player wakes up.")]
public class WelcomePanel : RustPlugin
{
private const string PanelName = "WelcomePanelUI";
private const string PermUse = "welcomepanel.use";
private PluginConfig config;
private readonly HashSet<ulong> shown = new HashSet<ulong>();
private readonly Dictionary<ulong, Timer> closeTimers = new Dictionary<ulong, Timer>();
private class PluginConfig
{
[JsonProperty("Title")]
public string Title = "WELCOME TO OUR SERVER";
[JsonProperty("Lines")]
public List<string> Lines = new List<string>
{
"Wipe schedule: first Thursday of every month",
"Group limit: 4 players",
"No cheating, no exploit abuse"
};
[JsonProperty("Links")]
public List<string> Links = new List<string>
{
"Discord: discord.gg/example",
"Website: example.com"
};
[JsonProperty("Auto Close Seconds")]
public float AutoCloseSeconds = 15f;
[JsonProperty("Cursor Enabled")]
public bool CursorEnabled = true;
[JsonProperty("Panel Anchor Min")]
public string AnchorMin = "0.28 0.30";
[JsonProperty("Panel Anchor Max")]
public string AnchorMax = "0.72 0.70";
[JsonProperty("Background Color")]
public string BackgroundColor = "0.10 0.10 0.10 0.92";
[JsonProperty("Title Color")]
public string TitleColor = "1.00 0.78 0.30 1.00";
[JsonProperty("Text Color")]
public string TextColor = "0.92 0.92 0.92 1.00";
[JsonProperty("Title Font Size")]
public int TitleFontSize = 22;
[JsonProperty("Text Font Size")]
public int TextFontSize = 14;
}
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>
{
["NoPermission"] = "You do not have permission to use this 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(PermUse, this);
}
private void OnPlayerSleepEnded(BasePlayer player)
{
if (player == null || player.IsNpc || !player.IsConnected) return;
ulong id = player.userID.Get();
if (!shown.Add(id)) return;
ShowPanel(player);
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null) return;
ulong id = player.userID.Get();
shown.Remove(id);
DestroyTimer(id);
CuiHelper.DestroyUi(player, PanelName);
}
private void ShowPanel(BasePlayer player)
{
DestroyTimer(player.userID.Get());
CuiHelper.DestroyUi(player, PanelName);
var c = new CuiElementContainer();
string panel = c.Add(new CuiPanel
{
Image = { Color = config.BackgroundColor },
RectTransform = { AnchorMin = config.AnchorMin, AnchorMax = config.AnchorMax },
CursorEnabled = config.CursorEnabled
}, "Overlay", PanelName);
c.Add(new CuiLabel
{
Text = { Text = config.Title, FontSize = config.TitleFontSize, Color = config.TitleColor, Align = TextAnchor.MiddleCenter },
RectTransform = { AnchorMin = "0.02 0.82", AnchorMax = "0.98 0.97" }
}, panel);
string body = config.Lines != null ? string.Join("\n", config.Lines.ToArray()) : "";
c.Add(new CuiLabel
{
Text = { Text = body, FontSize = config.TextFontSize, Color = config.TextColor, Align = TextAnchor.UpperLeft },
RectTransform = { AnchorMin = "0.05 0.45", AnchorMax = "0.95 0.80" }
}, panel);
string links = config.Links != null ? string.Join("\n", config.Links.ToArray()) : "";
c.Add(new CuiLabel
{
Text = { Text = links, FontSize = config.TextFontSize, Color = config.TextColor, Align = TextAnchor.UpperLeft },
RectTransform = { AnchorMin = "0.05 0.22", AnchorMax = "0.95 0.43" }
}, panel);
c.Add(new CuiButton
{
Button = { Command = "welcomepanel.close", Color = "0.70 0.20 0.20 1.00" },
RectTransform = { AnchorMin = "0.38 0.05", AnchorMax = "0.62 0.18" },
Text = { Text = "CLOSE", FontSize = 16, Align = TextAnchor.MiddleCenter }
}, panel);
CuiHelper.AddUi(player, c);
if (config.AutoCloseSeconds > 0f)
{
ulong id = player.userID.Get();
closeTimers[id] = timer.Once(config.AutoCloseSeconds, () =>
{
closeTimers.Remove(id);
if (player != null && player.IsConnected)
CuiHelper.DestroyUi(player, PanelName);
});
}
}
[ConsoleCommand("welcomepanel.close")]
private void CmdClose(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
DestroyTimer(player.userID.Get());
CuiHelper.DestroyUi(player, PanelName);
}
[ChatCommand("welcome")]
private void CmdWelcome(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
ShowPanel(player);
}
private void DestroyTimer(ulong id)
{
Timer existing;
if (closeTimers.TryGetValue(id, out existing))
{
existing?.Destroy();
closeTimers.Remove(id);
}
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
{
if (player != null)
CuiHelper.DestroyUi(player, PanelName);
}
foreach (var entry in closeTimers)
entry.Value?.Destroy();
closeTimers.Clear();
shown.Clear();
}
}
}Hooks used: OnPlayerSleepEnded, OnPlayerDisconnected
Config file
Created automatically on first load at oxide/config/WelcomePanel.json.
{
"Title": "WELCOME TO OUR SERVER",
"Lines": [
"Wipe schedule: first Thursday of every month",
"Group limit: 4 players",
"No cheating, no exploit abuse"
],
"Links": [
"Discord: discord.gg/example",
"Website: example.com"
],
"Auto Close Seconds": 15,
"Cursor Enabled": true,
"Panel Anchor Min": "0.28 0.30",
"Panel Anchor Max": "0.72 0.70",
"Background Color": "0.10 0.10 0.10 0.92",
"Title Color": "1.00 0.78 0.30 1.00",
"Text Color": "0.92 0.92 0.92 1.00",
"Title Font Size": 22,
"Text Font Size": 14
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/welcome | Reopens the welcome panel for yourself. | welcomepanel.use |
welcomepanel.close | Console command fired by the close button that destroys the panel and cancels its auto-close timer. | Everyone |
| Permission | Grants |
|---|---|
welcomepanel.use | Allows using /welcome to reopen the panel manually. |
How the code works
Track who has seen the panel
A
HashSet<ulong>namedshownrecords players who already received the panel during the current connection.OnPlayerSleepEnded(BasePlayer player)returns early when the player is null or already in the set, then adds the id and callsShowPanel.OnPlayerDisconnected(BasePlayer player, string reason)removes the id so a reconnecting player sees the panel again.Unloadclears the set.Build the panel from config
ShowPanelcreates aCuiElementContainerand adds aCuiPanelanchored betweenAnchorMinandAnchorMaxfromPanel Anchor Min/Panel Anchor Max, with the configured background colour andCursorEnabled = true. ACuiLabelholdsTitle, another holds theLinesjoined with\n, a third holds theLinksblock. Colours and font sizes are[JsonProperty]fields so admins can rebrand without touching code.Close button and command route
The close button is a
CuiButtonwhoseButton.Commandiswelcomepanel.close. A[ConsoleCommand("welcomepanel.close")]method readsarg.Player(), returns if null, and callsCuiHelper.DestroyUi(player, PanelName)after destroying the timeout timer. Because CUI buttons run console commands, this works without any chat input.Automatic close timer
The
Auto Close Secondsconfig value drives atimer.Once. The returnedTimeris stored in aDictionary<ulong, Timer>so it can be destroyed when the player closes the panel early, disconnects, or when the plugin unloads. Setting the value to0disables the timer and leaves the panel open until the button is pressed.Manual reopen command
[ChatCommand("welcome")]checkspermission.UserHasPermission(player.UserIDString, PermUse)and then destroys the existing panel and rebuilds it. Messages such as the no-permission reply go throughlang.RegisterMessagesinLoadDefaultMessagesand theMsghelper, so they can be translated inoxide/lang.Clean up on unload
UnloaditeratesBasePlayer.activePlayerList, callsCuiHelper.DestroyUi(player, PanelName)for each, destroys every timer in the dictionary, clears the dictionary and the session set. This prevents orphaned UI elements when the plugin reloads while a panel is open.
Install it on your server
Save WelcomePanel.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/WelcomePanel.json(Carbon:carbon/configs/WelcomePanel.json). After editing, reload it:oxide.reload WelcomePanel c.reload WelcomePanel # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default welcomepanel.use c.grant group default welcomepanel.use # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Show only to first-time players
Persist a data file of player ids so the panel appears only on a player's first connection to the server.
Generate this versionCountdown label
Add a small label that counts down the remaining seconds before the panel closes.
Generate this versionRule acceptance gate
Require the player to press an accept button before the panel closes, useful for rule acknowledgement.
Generate this version
FAQ
Why does the panel appear twice after a respawn?
OnPlayerSleepEnded fires after waking from sleep, including respawn wakes. The plugin tracks shown ids per connection, so it only shows once. If you want it on every respawn, remove the shown check and let the hook run each time.
Can I make the panel stay open until the player clicks?
Set Auto Close Seconds to 0 in the config. The timer is skipped entirely and only the close button removes the panel. Reload the plugin after editing the config file.
Does the panel block movement?
CursorEnabled = true shows the mouse cursor while the panel is open, which can interfere with aiming. Set Cursor Enabled to false in the config if players should keep control while reading.
How do I install it on Oxide or Carbon?
Place WelcomePanel.cs in oxide/plugins, run oxide.reload WelcomePanel from console, then edit oxide/config/WelcomePanel.json. Carbon loads the same file from its plugins folder and reads the same config path.
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 ui plugins
- Admin menuBuilds a CUI admin panel that opens with /am and renders one button per action defined in the config, each button running a console command throughโฆ
- Announcement bannerAdds an /announce chat command that draws a CUI banner at the top of every connected player's screen for a configurable duration, plus timedโฆOnServerInitializedOnPlayerConnected
- Command shortcut menuAdds a /menu chat command that opens a CUI grid of configurable buttons; each button closes the panel and runs its configured chat command for thatโฆ
Last updated Oct 26, 2026.
Customize welcome screen
Customize