OnSaveLoad
OnSaveLoad is called by SaveRestore.Load after all entities have been spawned from the world save file, and just before the server finishes the load and lets players reconnect.
object OnSaveLoad(Dictionary<BaseEntity, ProtoBuf.Entity> entities)Parameters
| Parameter | Type | Description |
|---|---|---|
entities | Dictionary<BaseEntity, ProtoBuf.Entity> | Map of every spawned BaseEntity being restored from the save file to its serialized ProtoBuf data. It is populated just before the entities are instantiated, so you can inspect or modify both the entity references and the raw saved proto data. |
Return value
Returning a value that is not a bool (including null) lets SaveRestore.Load continue normally with the map-load reporting and player connection setup. Returning a bool replaces the method's own result: true claims the load succeeded, false claims it failed, and either way the remaining vanilla load steps are skipped.
How it works
This hook fires once per world load, at the end of SaveRestore.Load. By that point the terrain, buildings, deployables and other saved entities have already been spawned (item.SpawnAsMapEntity() has run for each one), the dictionary has been filled, and the line Spawning N entities from save is about to be logged. The single parameter is a Dictionary<BaseEntity, ProtoBuf.Entity> mapping the freshly created entity objects to their deserialized proto records. Long-running loads are the main use case: you can count or filter entities, log map statistics, adjust freshly loaded entities (for example set flags, health or ownership), or delay your own setup logic until the world is fully there. Because the entities already exist, IsDestroyed will be false and net.ID is valid. Keep work fast; the server is still in its load phase and blocking here delays the whole map load. Use OnServerInitialized or OnNewSave if you only need init-time logic that does not depend on the entity dictionary.
Example plugin: Report a map load summary of entities
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Save Load Summary", "RustPluginGenerator", "1.0.0")]
[Description("Logs a short entity summary of the world right after the save file is loaded.")]
public class SaveLoadSummary : RustPlugin
{
private class PluginConfig
{
[JsonProperty("Print summary to server log")]
public bool PrintSummary { get; set; } = true;
[JsonProperty("Warn when entity count is above")]
public int WarnAbove { get; set; } = 200000;
}
private PluginConfig config;
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 object OnSaveLoad(Dictionary<BaseEntity, ProtoBuf.Entity> entities)
{
if (!config.PrintSummary || entities == null)
{
return null;
}
int buildings = 0;
int containers = 0;
int ovens = 0;
int doors = 0;
int players = 0;
foreach (KeyValuePair<BaseEntity, ProtoBuf.Entity> pair in entities)
{
BaseEntity entity = pair.Key;
if (entity == null || entity.IsDestroyed)
{
continue;
}
if (entity is BuildingBlock) buildings++;
else if (entity is StorageContainer) containers++;
else if (entity is BaseOven) ovens++;
else if (entity is Door) doors++;
else if (entity is BasePlayer) players++;
}
string line = string.Format(
"SaveLoadSummary: {0} entities restored (buildings {1}, containers {2}, ovens {3}, doors {4}, players {5})",
entities.Count, buildings, containers, ovens, doors, players);
if (entities.Count > config.WarnAbove)
{
PrintWarning(line + " - above configured threshold");
}
else
{
Puts(line);
}
return null;
}
}
}The plugin hooks OnSaveLoad, iterates the entity dictionary and counts a few common categories by checking against BuildingBlock, StorageContainer, BaseOven, Door and BasePlayer with the as operator. It writes the total and per-category counts to the server log so admins see how heavy a freshly restored map is. A config option toggles the summary and lets the admin set a threshold under which a warning is printed instead of a normal log line. All counters are local, so the hook returns nothing and the vanilla load path is untouched.
Common use cases
Map health overview after wipe
Count buildings, containers and deployables right after the save is loaded to spot maps that were restored from a bad backup or a much larger world than expected.
Post-load entity adjustment
Iterate the dictionary and tweak freshly loaded entities, for example resetting flags or ownership, before players reconnect.
Deferred setup for plugins
Some plugins need the full entity list to be present before they index buildings; this hook is the earliest point where that is guaranteed.
Pitfalls
Returning a bool breaks the load
The injection reads a returned bool and returns it to the caller, skipping the reporting and connection setup that follows. Always return null from this hook.
The dictionary is not authoritative forever
It reflects the save state at load time; entities spawned later by other plugins or by map changes are not in it. Rebuild any index when entities change.
Slow loops delay player joins
Heavy processing here blocks the server during map load. Keep it to simple iteration and defer expensive work to a timer after
OnServerInitialized.
Build a plugin with OnSaveLoad
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 OnSaveLoad
Generate