Skip to content
RustPluginGenerator

How to make a Rust auto broadcast plugin (timed chat announcements)

AutoBroadcast sends a list of chat messages to every online player on a repeating timer, in order or random order, and fills {players} and {time} placeholders before sending. It needs only Init to register data paths, OnServerInitialized to start the timer, and Unload to stop it.

Automatic chat announcements keep server rules, wipe dates and Discord links in front of players without an admin typing commands. The usual problem is either a hard-coded list of strings in the plugin or a single message that repeats forever, so players tune it out.

This plugin stores the messages and their color in the config so you never edit code to change a line. A single timer.Every loop walks the list in order or picks a random entry each time, depending on Random Order. Before each line is sent, {players} is replaced with BasePlayer.activePlayerList.Count and {time} with the server clock, and the result goes out via Server.Broadcast. Timing, prefix and color are all config keys, so the same file works for a small test server and a busy wipe-day community.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("Auto Broadcast", "RustPluginGenerator", "1.0.0")]
    [Description("Broadcasts a configurable list of chat messages on a timer with placeholder support.")]
    public class AutoBroadcast : RustPlugin
    {
        private const string PermReload = "autobroadcast.reload";

        private PluginConfig config;
        private Timer broadcastTimer;
        private int currentIndex;

        private class PluginConfig
        {
            [JsonProperty("Interval Seconds")]
            public float IntervalSeconds { get; set; } = 300f;

            [JsonProperty("Random Order")]
            public bool RandomOrder { get; set; } = false;

            [JsonProperty("Use Prefix")]
            public bool UsePrefix { get; set; } = true;

            [JsonProperty("Prefix")]
            public string Prefix { get; set; } = "[Server]";

            [JsonProperty("Color")]
            public string Color { get; set; } = "#ce422b";

            [JsonProperty("Messages")]
            public List<string> Messages { get; set; } = new List<string>
            {
                "Welcome! Type /help for available commands.",
                "There are {players} players online right now.",
                "Server time is {time}. Wipe information is on our Discord.",
                "Report rule breakers to admins with /report."
            };
        }

        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>
            {
                ["Reloaded"] = "Configuration reloaded. Interval set to {0} seconds.",
                ["Sent"] = "Next announcement sent.",
                ["NoMessages"] = "The message list is empty. Add entries to the config.",
                ["NoPermission"] = "You do not have permission to use this command.",
                ["Usage"] = "Usage: /broadcast reload | /broadcast next"
            }, 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(PermReload, this);
        }

        private void OnServerInitialized()
        {
            if (config.Messages == null || config.Messages.Count == 0)
            {
                PrintWarning(Msg("NoMessages"));
                return;
            }

            StartTimer();
        }

        private void Unload()
        {
            if (broadcastTimer != null)
            {
                broadcastTimer.Destroy();
                broadcastTimer = null;
            }
        }

        private void StartTimer()
        {
            if (broadcastTimer != null) broadcastTimer.Destroy();

            float interval = Math.Max(config.IntervalSeconds, 10f);
            broadcastTimer = timer.Every(interval, BroadcastNext);
        }

        private void BroadcastNext()
        {
            if (config.Messages == null || config.Messages.Count == 0) return;

            string raw;
            if (config.RandomOrder)
            {
                int pick = UnityEngine.Random.Range(0, config.Messages.Count);
                raw = config.Messages[pick];
            }
            else
            {
                if (currentIndex >= config.Messages.Count) currentIndex = 0;
                raw = config.Messages[currentIndex];
                currentIndex = (currentIndex + 1) % config.Messages.Count;
            }

            Server.Broadcast(Format(raw));
        }

        private string Format(string raw)
        {
            if (string.IsNullOrEmpty(raw)) return string.Empty;

            string message = raw
                .Replace("{players}", BasePlayer.activePlayerList.Count.ToString())
                .Replace("{time}", DateTime.Now.ToString("HH:mm"));

            if (config.UsePrefix && !string.IsNullOrEmpty(config.Prefix))
            {
                message = config.Prefix + " " + message;
            }

            if (!string.IsNullOrEmpty(config.Color))
            {
                message = "<color=" + config.Color + ">" + message + "</color>";
            }

            return message;
        }

        [ChatCommand("broadcast")]
        private void CmdBroadcast(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;

            if (!permission.UserHasPermission(player.UserIDString, PermReload))
            {
                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":
                    Interface.Oxide.ReloadPlugin(Name);
                    break;
                case "next":
                    if (config.Messages == null || config.Messages.Count == 0)
                    {
                        SendReply(player, Msg("NoMessages", player.UserIDString));
                        return;
                    }
                    BroadcastNext();
                    SendReply(player, Msg("Sent", player.UserIDString));
                    break;
                default:
                    SendReply(player, Msg("Usage", player.UserIDString));
                    break;
            }
        }
    }
}

Hooks used: OnServerInitialized

Config file

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

AutoBroadcast.json
{
  "Interval Seconds": 300,
  "Random Order": false,
  "Use Prefix": true,
  "Prefix": "[Server]",
  "Color": "#ce422b",
  "Messages": [
    "Welcome! Type /help for available commands.",
    "There are {players} players online right now.",
    "Server time is {time}. Wipe information is on our Discord.",
    "Report rule breakers to admins with /report."
  ]
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/broadcast reloadReloads the config from disk and restarts the broadcast timer with the new interval.autobroadcast.reload
/broadcast nextSends the next queued message to everyone immediately, without advancing the timer.autobroadcast.reload
Permissions
PermissionGrants
autobroadcast.reloadGrants the /broadcast commands to reload the config or push the next announcement.

How the code works

  1. Config shape

    PluginConfig holds Interval Seconds, Random Order, Prefix, Use Prefix, Color and a Messages list of strings. Each field carries a [JsonProperty] name so the file stays readable. LoadDefaultConfig creates the defaults, and LoadConfig falls back to them with a warning if the file fails to parse, then writes the result back with SaveConfig so new keys appear after an update.

  2. Starting the loop

    OnServerInitialized reads the interval, clamps it to a sane minimum, and calls timer.Every(interval, BroadcastNext), keeping the Timer in a field. Starting there rather than in Init means the timer does not tick while the server is still loading plugins. If Messages is empty the plugin logs a warning once and returns without scheduling anything.

  3. Order and random mode

    An integer index points at the next message. In order mode the index increments after each send and wraps with % Messages.Count. In random mode BroadcastNext picks UnityEngine.Random.Range(0, count) and does not touch the index, so the same line can appear twice in a row. Both paths go through one method, so /broadcast next behaves identically to a scheduled tick.

  4. Placeholders

    Before the message leaves the plugin, Format replaces {players} with BasePlayer.activePlayerList.Count and {time} with DateTime.Now.ToString("HH:mm"), then applies the prefix and color when Use Prefix is on. Replacement happens per send, not at load time, so a {players} line shows the current headcount rather than the value from server start.

  5. Sending to everyone

    Server.Broadcast(string) writes a chat line to every connected player, which suits a message that has no owner and no reply target. Wrapping the tag in <color=#hex></color> works because chat renders rich text tags. The method is used instead of iterating BasePlayer.activePlayerList with SendReply, so a single call handles joins and leaves without extra bookkeeping.

  6. Commands and cleanup

    /broadcast reload re-reads config, destroys the running timer and schedules a new one, so an interval change applies without a plugin reload. /broadcast next calls the same send method once. Both check permission.UserHasPermission against autobroadcast.reload, registered in Init. Unload destroys the timer reference so a reload does not leave two loops sending the same lines.

Install it on your server

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

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

Variations

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

FAQ

How do I install it on Oxide or Carbon?

Drop AutoBroadcast.cs into oxide/plugins, wait for the console to report it compiled, then edit oxide/config/AutoBroadcast.json. Carbon loads the same file from its plugin folder. Reload the plugin or run /broadcast reload after editing the config.

Why do my `{players}` placeholders show as text?

Placeholder replacement is case-sensitive and only recognizes the exact tokens {players} and {time}. A typo such as {Players} is sent literally. Curly braces are also rendered as-is by chat, so you should see the token unchanged rather than an error.

Does this survive a plugin reload without duplicating messages?

Yes. Unload destroys the stored Timer, and OnServerInitialized creates a fresh one on the next load. If you edit the code while the server is running, wait for the reload to finish before testing, otherwise two timers can briefly run.

Can I send to a single team or player instead?

Not with this build; Server.Broadcast always targets everyone. For scoped messages you would loop BasePlayer.activePlayerList, filter with currentTeam or a permission check, and call SendReply on each match. That iteration is slower but gives full control over the audience.

Where does the time in `{time}` come from?

It uses DateTime.Now on the machine running the server, formatted as hours and minutes, so it reflects the host OS time zone. Set the server's time zone or convert to UTC in the Format method if your admins are in a different region.

Customize it for your server

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

Prompt: Create an Oxide plugin called AutoBroadcast that broadcasts configurable chat messages every N seconds in order or random order, replacing {players} and {time}, with a /broadcast reload command behind a permission.
Customize for my server

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

Last updated Oct 26, 2026.

Customize automatic announcements

Customize