OnEntitySaved
OnEntitySaved is called by Oxide right before an entity is serialized for network transmission, and only when the save targets a specific connection rather than the disk.
void OnEntitySaved(BaseNetworkable baseNetworkable, BaseNetworkable.SaveInfo saveInfo)Parameters
| Parameter | Type | Description |
|---|---|---|
baseNetworkable | BaseNetworkable | The entity being serialized for the network. Cast it to the concrete type (StorageContainer, BuildingBlock, BasePlayer, etc.) to inspect or adjust it; it can be null. |
saveInfo | BaseNetworkable.SaveInfo | The save object that holds the network payload plus context such as forConnection. Its Msg is the network message being written, and forConnection identifies which client or snapshot the save is for. |
Return value
The game method returns void and the hook result is ignored, so returning a value has no effect. Use the hook to inspect or modify state; you cannot cancel the network save through the return value.
How it works
The hook fires from Oxide's own save routing, not from a patched game method. Whenever an entity is written to a network message for a particular client (the forConnection value is non-null), the hook runs before the payload leaves the server. Saves to disk or to the general broadcast path are skipped because forConnection is null, and so are calls that happen before the server is fully initialized.
baseNetworkable is the entity being saved, so you can cast it to BasePlayer, BuildingBlock, StorageContainer, or another concrete type. saveInfo.forConnection tells you which client will receive the update. Declaring the first parameter as a more specific type makes Oxide skip calls for any other entity, which is useful when you only care about one class.
Because this fires for every network save of an entity, keep the handler cheap: no allocations, no logging. It is a practical place to observe visibility or adjust what a client is about to receive, but not to block the save itself.
Example plugin: Count storage container network saves per owner and report periodically
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("EntitySavedAudit", "RustPluginGenerator", "1.0.0")]
[Description("Counts network saves of storage containers per player and reports them periodically.")]
public class EntitySavedAudit : RustPlugin
{
private PluginConfig config;
private readonly Dictionary<ulong, int> saveCounts = new Dictionary<ulong, int>();
private Timer reportTimer;
private class PluginConfig
{
[JsonProperty("Enable auditing")]
public bool Enabled = true;
[JsonProperty("Report interval (seconds)")]
public float Interval = 10f;
}
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);
private void OnServerInitialized()
{
if (!config.Enabled) return;
reportTimer = timer.Every(Mathf.Max(1f, config.Interval), Report);
}
private void OnEntitySaved(BaseNetworkable baseNetworkable, BaseNetworkable.SaveInfo saveInfo)
{
if (!config.Enabled) return;
if (baseNetworkable == null) return;
var container = baseNetworkable as StorageContainer;
if (container == null) return;
var ownerId = container.OwnerID;
if (ownerId == 0) return;
saveCounts.TryGetValue(ownerId, out var current);
saveCounts[ownerId] = current + 1;
}
private void Report()
{
if (saveCounts.Count == 0) return;
foreach (var kvp in saveCounts)
{
Puts($"Container saves for {kvp.Key}: {kvp.Value}");
}
saveCounts.Clear();
}
private void Unload()
{
if (reportTimer != null && !reportTimer.Destroyed) reportTimer.Destroy();
reportTimer = null;
saveCounts.Clear();
}
}
}The plugin counts network saves of StorageContainer entities, keyed by container.OwnerID, and ignores entities whose OwnerID is 0. OnEntitySaved receives each BaseNetworkable and SaveInfo, casts the networkable to StorageContainer, and increments a per-owner counter. A timer set to the configured Interval (default 10 seconds, minimum 1 second) writes each owner's total with Puts and clears the counts, so logging happens only on the timer rather than inside the save hook. The Enabled config setting turns auditing on or off, and Unload destroys the timer.
Common use cases
Diagnose bandwidth hotspots
Count savings per entity type to find which prefabs dominate network traffic to a given client. A modded server with many custom entities benefits from this visibility.
Verify visibility filtering
Confirm that containers, doors, or markers are only serialized to the intended connections after you've applied custom rules elsewhere.
Instrument a custom entity system
Track when your own
BaseNetworkablesubclasses are saved to clients to detect components that are churning too many updates.
Pitfalls
Hook does not fire for disk saves
Saves with a null
forConnectionare skipped, so the hook is useless for inspecting data file persistence. UseOnServerSaveorOnNewSavefor that instead.Fires very often
Every network save of every entity triggers the hook. Logging, LINQ, or string allocation inside the handler will hurt performance on busy servers.
Cannot block the payload
The return value is ignored and the save proceeds. If you need to suppress or alter data, do it earlier in the pipeline rather than relying on this hook.
Build a plugin with OnEntitySaved
Describe what should happen and get a compiled plugin that uses this hook.
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 OnEntitySaved
Generate