Skip to content
RustPluginGenerator

OnPlayerSetInfo

OnPlayerSetInfo is called when the server receives or sets a client info key/value pair for a player, once per entry in the ClientReady packet during join and again whenever BasePlayer.SetInfo is invoked on a connected player.

Signature
void OnPlayerSetInfo(Network.Connection connection, string name, string value)

Parameters

Hook parameters
ParameterTypeDescription
connectionNetwork.ConnectionThe network connection of the player whose client info is being written. Use connection.userid to identify the player; connection.info is the object that the game writes name/value into after the hook runs.
namestringThe key of the client info entry, for example a player setting like 'global.menu_style' or similar client-sent keys. Match against known keys to target specific settings.
valuestringThe string value the client is submitting for that key. Inspect it to log, validate or react to what the client is sending.

Return value

The hook runs before the game writes the info entry and any return value is ignored, so the game always proceeds to store the value. Returning nothing or returning a value makes no difference to the stored setting.

How it works

The hook fires in two places. First, while processing the ClientReady packet from a joining client, the server loops over every clientInfo entry and calls the hook before storing it via connection.info.Set. Second, BasePlayer.SetInfo(string key, string val) calls it whenever the game updates a connected player's info entry. In both cases the hook runs before the value is written, so you see what the client is about to store.

connection identifies the player (connection.userid), and name/value describe the setting. All three arguments arrive as-is, so you do not narrow anything by parameter type here. The hook cannot cancel the write: the game stores the entry after the hook runs regardless of what you do, so treat it as an observer for logging, metrics or side effects.

Client info arrives in a burst at join time, so this hook can fire many times per player within a fraction of a second. Keep the handler cheap, avoid per-call Puts, and throttle any output. It is also useful for building a snapshot of a player's client configuration as they connect.

Example plugin: Log and audit client info keys on join

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

namespace Oxide.Plugins
{
    [Info("Client Info Auditor", "RustPluginGenerator", "1.0.0")]
    [Description("Collects client info key/value pairs a player submits and reports a summary to admins.")]
    public class ClientInfoAuditor : RustPlugin
    {
        private class PluginConfig
        {
            [JsonProperty("Report delay (seconds)")]
            public float ReportDelay = 3f;

            [JsonProperty("Suppress key prefix")]
            public string SuppressKeyPrefix = "global.";
        }

        private PluginConfig config;
        private readonly Dictionary<ulong, Dictionary<string, string>> pendingInfo = new Dictionary<ulong, Dictionary<string, string>>();
        private readonly Dictionary<ulong, Timer> reportTimers = new Dictionary<ulong, Timer>();

        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>
            {
                ["Report"] = "{0} submitted {1} client info entries: {2}",
                ["ReportHeader"] = "Client info audit"
            }, this);
        }

        private string Msg(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);

        private void OnPlayerSetInfo(Network.Connection connection, string name, string value)
        {
            if (connection == null || string.IsNullOrEmpty(name)) return;

            if (!string.IsNullOrEmpty(config.SuppressKeyPrefix) && name.StartsWith(config.SuppressKeyPrefix, StringComparison.OrdinalIgnoreCase))
                return;

            ulong userId = connection.userid;
            if (userId == 0) return;

            Dictionary<string, string> entries;
            if (!pendingInfo.TryGetValue(userId, out entries))
            {
                entries = new Dictionary<string, string>();
                pendingInfo[userId] = entries;
            }
            entries[name] = value;

            Timer existing;
            if (reportTimers.TryGetValue(userId, out existing) && existing != null)
                existing.Destroy();

            reportTimers[userId] = timer.Once(Mathf.Max(0.5f, config.ReportDelay), () => SendSummary(userId));
        }

        private void SendSummary(ulong userId)
        {
            reportTimers.Remove(userId);

            Dictionary<string, string> entries;
            if (!pendingInfo.TryGetValue(userId, out entries) || entries.Count == 0) return;
            pendingInfo.Remove(userId);

            var parts = new List<string>();
            foreach (var pair in entries)
                parts.Add(pair.Key + "=" + pair.Value);

            string name = "Player " + userId;
            var player = BasePlayer.FindByID(userId);
            if (player != null) name = player.displayName;

            string text = Msg("Report", null, name, entries.Count, string.Join(", ", parts.ToArray()));

            foreach (var admin in BasePlayer.activePlayerList)
            {
                if (admin != null && admin.IsConnected && admin.IsAdmin)
                    admin.ChatMessage(text);
            }
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (player == null) return;

            ulong userId = player.userID.Get();
            Timer timer;
            if (reportTimers.TryGetValue(userId, out timer) && timer != null)
                timer.Destroy();
            reportTimers.Remove(userId);
            pendingInfo.Remove(userId);
        }

        private void Unload()
        {
            foreach (var timer in reportTimers.Values)
                timer?.Destroy();
            reportTimers.Clear();
            pendingInfo.Clear();
        }
    }
}

The plugin collects every client info key/value a player submits and prints a compact summary to admins once the join burst settles. OnPlayerSetInfo stores each entry in a per-user dictionary and (re)arms a short timer, so multiple back-to-back calls from the ClientReady packet produce a single chat report instead of one line per key. A config flag SuppressKeyPrefix lets admins ignore noisy keys before they are recorded, and ReportDelay controls how long after the last entry the summary fires. Each pending timer is tracked in a dictionary and destroyed in Unload, and the stored data is cleared when a player disconnects via OnPlayerDisconnected. Nothing is written to console during the hook itself.

Common use cases

  • Audit client settings on connect

    Record the client info keys each player submits so admins can compare configurations between players or spot unusual settings.

  • Trigger admin notifications

    Send a single summary to online admins when a player joins with a particular key present, without spamming one message per entry.

  • Build per-player profiles

    Persist collected info entries per user and use them later for reporting or compatibility checks in other plugins.

Pitfalls

  • It fires many times at once

    The ClientReady packet contains several entries, so the hook runs once per key in quick succession. Batch your handling with a short timer instead of acting on every call.

  • You cannot block the write

    The game stores the value after the hook runs and ignores any return value. Do not treat this hook as a way to reject or rewrite a client setting.

  • Value is client-supplied

    value comes from the joining client and is not validated by the game at this point. Do not trust it for security decisions; treat it as untrusted input.

Build a plugin with OnPlayerSetInfo

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Audit client settings on connect using the OnPlayerSetInfo hook, with a config file and a permission
Generate this plugin

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

Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.

Build a plugin with OnPlayerSetInfo

Generate