Skip to content
RustPluginGenerator

OnSignUpdated

OnSignUpdated is called after a player saves new artwork on a sign, photo frame or carvable pumpkin. It fires once the new PNG has been stored and the entity is marked for a network update.

Signature
void OnSignUpdated(Signage signage, BasePlayer player, int num)

Parameters

Hook parameters
ParameterTypeDescription
signageSignageThe sign entity whose texture was just updated. It is a Signage, so it can also be a PhotoFrame or CarvablePumpkin since both derive from it.
playerBasePlayerThe BasePlayer who saved the new drawing. May be null if the update came from a non-player source.
numintThe texture slot index that was written. Signs with multiple faces use this to indicate which face changed; the two-argument PhotoFrame variant has no index.

Return value

The return value is ignored. UpdateSign has already completed its storage and network work before the hook fires, so whatever your method returns does not change the sign's texture or the original code path.

How it works

This hook runs at the end of Signage.UpdateSign, after the uploaded image has been written to FileStorage, the analytics event has been recorded, the edit has been logged, and SendNetworkUpdate has already been called. That means the new texture is live by the time your plugin sees the call, so you cannot cancel or revert the drawing here. The signage argument is the entity being drawn on; because PhotoFrame and CarvablePumpkin both derive from Signage, a single OnSignUpdated(Signage signage, BasePlayer player, int num) method catches all three cases. The player argument is the one who hit Save; it can be null in theory. The num argument is the texture slot that changed, useful on multi-faced signs. Declaring a narrower first parameter type (for example PhotoFrame) makes Oxide only call your method for that subtype. The PhotoFrame and CarvablePumpkin variants pass only two arguments, so if you need the slot index, remember their calls will not match a three-argument method. This hook is a good place to enforce sign rules, create audit records, or trigger gameplay reactions.

Other signatures

Oxide also calls OnSignUpdated from other places in the game with these parameter types. Declare the overload you need, or several:

  • void OnSignUpdated(PhotoFrame photoFrame, BasePlayer player)
  • void OnSignUpdated(CarvablePumpkin carvablePumpkin, BasePlayer player)

Example plugin: Limit sign edits per player with a timed cooldown

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

namespace Oxide.Plugins
{
    [Info("Sign Edit Cooldown", "RustPluginGenerator", "1.0.0")]
    [Description("Tracks sign edits per player and enforces a rolling edit limit.")]
    public class SignEditCooldown : RustPlugin
    {
        private const string PermBypass = "signeditcooldown.bypass";

        private PluginConfig config;
        private StoredData data;
        private Timer resetTimer;

        private class PluginConfig
        {
            [JsonProperty("Max edits per window")]
            public int MaxEdits { get; set; } = 20;

            [JsonProperty("Window length in seconds")]
            public float WindowSeconds { get; set; } = 300f;
        }

        private class StoredData
        {
            [JsonProperty("Edit counts")]
            public Dictionary<string, int> Counts { get; set; } = new Dictionary<string, int>();
        }

        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>
            {
                ["LimitReached"] = "You have edited signs {0} times recently. Please wait before editing again.",
                ["EditCounted"] = "Sign edit recorded ({0}/{1})."
            }, 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(PermBypass, this);
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private void OnServerInitialized()
        {
            resetTimer = timer.Every(config.WindowSeconds, ResetCounts);
        }

        private void OnSignUpdated(Signage signage, BasePlayer player, int num)
        {
            if (player == null || data == null) return;
            if (permission.UserHasPermission(player.UserIDString, PermBypass)) return;

            string id = player.UserIDString;
            int current;
            data.Counts.TryGetValue(id, out current);
            current++;
            data.Counts[id] = current;

            if (current == config.MaxEdits + 1)
            {
                player.ChatMessage(Msg("LimitReached", id, config.MaxEdits));
            }
        }

        private void ResetCounts()
        {
            if (data == null) return;
            data.Counts.Clear();
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        private void Unload()
        {
            if (resetTimer != null)
            {
                resetTimer.Destroy();
                resetTimer = null;
            }
            if (data != null) Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }
    }
}

The plugin keeps a rolling edit count per player and blocks further saves once they exceed a configured limit inside a time window. OnSignUpdated runs after the drawing is saved, so it cannot refuse the current one; instead it records the update and, when the player goes over the limit, marks them as rate-limited in a dictionary. A permission signeditcooldown.bypass skips the check for admins and trusted players. The dictionary is persisted to oxide/data and the timestamps reset on a configurable interval via a timer that is destroyed in Unload. Chat feedback goes through the language file so messages can be localised. Because the hook fires on every save, the plugin does no logging inside it, only bookkeeping.

Common use cases

  • Audit sign artwork changes

    Record who changed which sign, at which position, and when, writing the entries to an Oxide data file for staff review of griefing or offensive drawings.

  • Enforce drawing limits

    Count edits per player and warn or temporarily mute (via a flag you check before other actions) players who spam updates on many signs.

  • Trigger gameplay reactions

    Award a small amount of a custom currency the first time a player completes a sign, or alert a team that its base sign has been redecorated.

Pitfalls

  • Fires after the save

    The texture is already stored and the entity already sent a network update when this hook runs, so you cannot block or undo the current drawing from here.

  • Subtype argument mismatch

    PhotoFrame and CarvablePumpkin call the hook with only two arguments, so a three-argument signature will not run for those entities. Declare the two-argument overload if you need coverage.

  • High-frequency saves

    Players can save repeatedly and each save triggers this hook. Keep handlers cheap and avoid per-save logging to chat or console.

Build a plugin with OnSignUpdated

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

Prompt: Audit sign artwork changes using the OnSignUpdated 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 OnSignUpdated

Generate