Skip to content
RustPluginGenerator

OnClanLogoChanged

OnClanLogoChanged is called after Rust's LocalClan.SetLogo coroutine successfully writes a new clan logo to the backend database and marks the clan data as changed.

Signature
void OnClanLogoChanged(LocalClan localClan, byte[] newLogo, ulong bySteamId)

Parameters

Hook parameters
ParameterTypeDescription
localClanLocalClanThe LocalClan instance whose logo was just written to the backend database. Use its ClanId, Name and members to identify or resolve the clan.
newLogobyte[]The raw PNG bytes that were stored as the new clan logo. This is the same array handed to the database update, so you can validate size or format here.
bySteamIdulongSteam ID (ulong) of the player who changed the logo, as passed by the game to SetLogo. It is not guaranteed to still be on the server.

Return value

The hook's return value is ignored; the game continues and sets ClanResult.Success regardless of what you return. Returning null is fine and is the conventional choice.

How it works

The hook fires inside the LocalClan/<SetLogo>d__60.MoveNext state machine, on the success branch only. The game first confirms the backend returned true from UpdateClanLogo, appends a set_logo entry to the clan log, calls localClan.Changed(ClanDataSource.Logo), and then invokes OnClanLogoChanged with the clan, the logo byte array and the acting Steam ID. A failed write never reaches the hook, so you do not need to handle failure paths here.

localClan gives you the server-side clan object; newLogo is the exact byte array that was persisted, which is useful for size checks, hashing or copying into your own storage; bySteamId identifies the actor for audit purposes. Because the call site is generic over LocalClan, declaring a more specific parameter type has no practical effect; keep LocalClan.

Practical uses are auditing (log who changed a logo and how large it is), moderation (flag oversized uploads after the fact) and syncing clan branding to your own data files or UI.

Example plugin: Audit clan logo changes to a data file

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

namespace Oxide.Plugins
{
    [Info("Clan Logo Audit", "RustPluginGenerator", "1.0.0")]
    [Description("Records every successful clan logo change to a data file for moderation and reporting")]
    public class ClanLogoAudit : RustPlugin
    {
        private const string DataFileName = "ClanLogoAudit";
        private const int MaxEntries = 200;

        private PluginConfig config;
        private AuditData data;

        private class PluginConfig
        {
            [JsonProperty("Notify clan members in chat")]
            public bool NotifyMembers { get; set; } = true;

            [JsonProperty("Warn staff when logo exceeds max bytes")]
            public bool WarnOnLargeLogo { get; set; } = true;

            [JsonProperty("Maximum logo size in bytes")]
            public int MaxLogoBytes { get; set; } = 262144;
        }

        private class AuditEntry
        {
            [JsonProperty("timestamp")]
            public string Timestamp { get; set; }

            [JsonProperty("clanId")]
            public long ClanId { get; set; }

            [JsonProperty("clanName")]
            public string ClanName { get; set; }

            [JsonProperty("bySteamId")]
            public ulong BySteamId { get; set; }

            [JsonProperty("byName")]
            public string ByName { get; set; }

            [JsonProperty("logoBytes")]
            public int LogoBytes { get; set; }
        }

        private class AuditData
        {
            [JsonProperty("entries")]
            public List<AuditEntry> Entries { get; set; } = new List<AuditEntry>();
        }

        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>
            {
                ["LogoUpdated"] = "Your clan logo was updated ({0} bytes).",
                ["MemberNotice"] = "The logo for clan {0} was updated by {1}.",
                ["LargeLogo"] = "Clan {0} ({1}) uploaded a logo of {2} bytes, above the {3} byte limit."
            }, this);
        }

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

        private void Loaded()
        {
            data = Interface.Oxide.DataFileSystem.ReadObject<AuditData>(DataFileName) ?? new AuditData();
        }

        private void Unload()
        {
            Interface.Oxide.DataFileSystem.WriteObject(DataFileName, data);
        }

        private void OnClanLogoChanged(LocalClan localClan, byte[] newLogo, ulong bySteamId)
        {
            if (localClan == null || newLogo == null) return;

            BasePlayer actor = BasePlayer.FindAwakeOrSleeping(bySteamId.ToString());
            string actorName = actor != null ? actor.displayName : bySteamId.ToString();

            var entry = new AuditEntry
            {
                Timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"),
                ClanId = localClan.ClanId,
                ClanName = localClan.Name,
                BySteamId = bySteamId,
                ByName = actorName,
                LogoBytes = newLogo.Length
            };

            data.Entries.Add(entry);
            if (data.Entries.Count > MaxEntries)
                data.Entries.RemoveAt(0);

            if (actor != null && actor.IsConnected)
                actor.ChatMessage(Msg("LogoUpdated", actor.UserIDString, newLogo.Length));

            if (!config.NotifyMembers || localClan.Members == null) return;

            foreach (var memberInfo in localClan.Members)
            {
                if (memberInfo.SteamId == 0UL) continue;
                ulong memberId = memberInfo.SteamId;
                BasePlayer member = BasePlayer.FindByID(memberId);
                if (member == null || !member.IsConnected) continue;
                if (actor != null && member.userID == actor.userID) continue;
                member.ChatMessage(Msg("MemberNotice", member.UserIDString, localClan.Name, actorName));
            }

            if (config.WarnOnLargeLogo && newLogo.Length > config.MaxLogoBytes)
                PrintWarning(Msg("LargeLogo", null, localClan.Name, localClan.ClanId, newLogo.Length, config.MaxLogoBytes));
        }
    }
}

The plugin records every successful clan logo change into oxide/data/ClanLogoAudit.json, keeping the last 200 entries. On OnClanLogoChanged it reads the clan ID and name from localClan, resolves the acting player's name via BasePlayer.FindAwakeOrSleeping when possible (falling back to the raw Steam ID), and stores the logo's byte length plus the UTC timestamp. A chat message informs the acting player and online clan members that their logo was updated. The file is written through Interface.Oxide.DataFileSystem and saved once when the plugin unloads, so a busy server does not write to disk on every logo change.

Common use cases

  • Moderation trail

    Store the Steam ID, clan and logo size of each change so staff can review who uploaded abusive logos after the fact.

  • Logo size policing

    Compare newLogo.Length against your limit and notify staff or remove the clan tag of offenders manually from the recorded list.

  • External branding sync

    Copy the logo bytes into your own data file so a web dashboard or companion tool can display clan logos without touching the game's database.

Pitfalls

  • Only fires on success

    If the backend rejects the logo the hook never runs, so a missing entry in your log does not mean the player did not try.

  • Player may be offline

    bySteamId is just an ID; BasePlayer.FindAwakeOrSleeping can return null. Always fall back to the raw ID string in your records.

  • Do not block the action

    The database write already happened before the hook fires, so returning a value or throwing does not undo the logo change.

Build a plugin with OnClanLogoChanged

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

Prompt: Moderation trail using the OnClanLogoChanged 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 OnClanLogoChanged

Generate