OnEntitySpawned
OnEntitySpawned is called after BaseNetworkable.Spawn completes server initialization for an entity, right before its first immediate network update is sent. It fires for every networked entity on the server, including players, deployables, and map-spawned objects.
void OnEntitySpawned(BaseNetworkable entity)Parameters
| Parameter | Type | Description |
|---|---|---|
entity | BaseNetworkable | The BaseNetworkable that just finished spawning. Cast it to a concrete type (BasePlayer, BuildingBlock, StorageContainer, etc.) or inspect ShortPrefabName to decide whether the spawn is interesting. |
Return value
The return value is ignored. BaseNetworkable.Spawn is void and continues with SendNetworkUpdateImmediate and its remaining steps regardless of what your method returns.
How it works
The hook fires inside BaseNetworkable.Spawn, after ServerInit, PostInitShared, network group assignment and the isSpawned flag, but before SendNetworkUpdateImmediate. Because players, NPCs, deployables, dropped items, vehicles and map entities all go through this path, it fires very frequently on a busy server.
Use the entity argument to filter by type: cast with as BasePlayer, as BuildingBlock, as StorageContainer, as BaseOven and so on. entity.ShortPrefabName and entity.PrefabName are cheap string checks for prefab-specific logic. entity.net.ID.Value gives you a stable network id for tracking.
Declaring a more specific parameter type (for example BasePlayer) narrows dispatch: Oxide skips your method when the spawned object is not an instance of that type. This is the cheapest way to only react to players or to a specific entity class.
Because it fires so often, avoid heavy work here. Do not call Puts, PrintWarning, or network/UI updates unconditionally. Instead, set a flag, queue the entity id, or schedule work with NextTick or a short timer. Entities removed in the same frame may still trigger this hook, so null-check and verify IsDestroyed before acting.
Example plugin: Limit deployables of a prefab per player
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("SpawnLimitGuard", "RustPluginGenerator", "1.0.0")]
[Description("Enforces per-player deployable limits for configured prefabs")]
public class SpawnLimitGuard : RustPlugin
{
private const string PermUse = "spawnlimitguard.use";
private PluginConfig config;
private readonly Dictionary<string, int> limits = new Dictionary<string, int>();
private class PluginConfig
{
[JsonProperty("Enable deployable limit checks")]
public bool Enabled = true;
[JsonProperty("Prefab limits (ShortPrefabName:MaxPerPlayer)")]
public Dictionary<string, int> PrefabLimits = new Dictionary<string, int>
{
["furnace"] = 1,
["furnace.large"] = 1,
["small_storage_box"] = 3,
["autoturret_deployed"] = 1
};
[JsonProperty("Use permission")]
public bool UsePermission = true;
}
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 already have the maximum number of {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(PermUse, this);
limits.Clear();
foreach (var entry in config.PrefabLimits)
{
if (string.IsNullOrEmpty(entry.Key) || entry.Value <= 0) continue;
limits[entry.Key] = entry.Value;
}
}
private void OnEntitySpawned(BaseNetworkable entity)
{
if (!config.Enabled || entity == null) return;
var baseEntity = entity as BaseEntity;
if (baseEntity == null || baseEntity.OwnerID == 0) return;
var prefab = entity.ShortPrefabName;
if (prefab == null) return;
int max;
if (!limits.TryGetValue(prefab, out max)) return;
var owner = BasePlayer.FindByID(baseEntity.OwnerID);
if (owner == null || owner.IsNpc) return;
if (config.UsePermission && !permission.UserHasPermission(owner.UserIDString, PermUse)) return;
int count = 0;
foreach (var other in BaseNetworkable.serverEntities)
{
var candidate = other as BaseEntity;
if (candidate == null || candidate == baseEntity) continue;
if (candidate.OwnerID != baseEntity.OwnerID) continue;
if (candidate.ShortPrefabName != prefab) continue;
count++;
}
if (count < max) return;
var entityToKill = baseEntity;
var player = owner;
var limit = max;
var name = prefab;
NextTick(() =>
{
if (entityToKill != null && !entityToKill.IsDestroyed)
{
entityToKill.Kill();
}
if (player != null && player.IsConnected)
{
player.ChatMessage(Msg("LimitReached", player.UserIDString, name, limit));
}
});
}
}
}The plugin declares the hook with BaseNetworkable and immediately filters to BaseEntity so it only considers world entities, skipping items and other networkables. The OwnerID from the entity identifies the player who placed it, so the check works for deployables like furnaces and boxes. A config list of Prefab:Max pairs is parsed at load time into the tracked prefabs, and CheckLimit activates only when the entity's ShortPrefabName matches a tracked rule and the player has permission. When the count exceeds the limit, the plugin schedules a next-tick kill and sends a chat warning through the language file. Untracked prefabs exit immediately, keeping the per-spawn cost to a single dictionary lookup.
Common use cases
Cap deployables per player
Count existing entities by prefab and OwnerID, and remove new ones that exceed a configured limit. Useful for furnace, box or turret caps on small servers.
React to specific map entities
Detect a supply drop, Bradley APC or Patrol Helicopter by
ShortPrefabNameand start a tracking timer, broadcast a warning, or register the entity id for later lookup.Auto-configure new containers
When a
StorageContainerspawns as an owned deployable, apply a skin, lock the loot panel, or tag the entity id in a data file so admins can review newly placed storage.
Pitfalls
Fires for almost everything
Players, NPCs, dropped items, loot and map props all hit this hook. Without an early type or prefab filter, your callback runs hundreds of times per minute and costs CPU.
Killing too early
Calling
Kill()directly inside the hook can leave a partially networked object behind. Schedule the kill withNextTickortimer.Onceso the entity finishes its spawn sequence first.OwnerID is not always a player
Map-spawned objects and entities owned by the server can have an
OwnerIDthat does not map to a connected player. ResolveBasePlayer.FindByIDand handle a null result before sending messages.
Plugin recipes that use OnEntitySpawned
- How to make a Rust corpse loot protection plugin (solo and team lock)Locks a dead player's corpse and dropped backpack to the owner and their current team for a configurable number of minutes. It watches spawned containers with OnEntitySpawned and blocks other players in CanLootEntity, with a permission-based admin bypass.
- How to make a Rust heli and Bradley loot lock plugin (PvE ownership)Tracks damage dealt to the Patrol Helicopter and Bradley APC per player and locks the crates they drop for a configurable time, allowing only the top damager and their team to loot. Uses OnEntityTakeDamage, OnEntityDeath, OnEntitySpawned and CanLootEntity.
- How to make a Rust no-NPC zones plugin (remove scientists)Defines named world zones where newly spawned scientists and animals are deleted immediately. It uses the OnEntitySpawned hook to check each new entity against the configured zones.
- How to make a Rust NPC health multiplier plugin (per-prefab scaling)Scales the health of scientists and animals when they spawn by reading a per-prefab multiplier from the config, using the OnEntitySpawned hook to detect NPC entities and heal them to the scaled target.
- How to make a Rust sleeping bag cooldown plugin (Oxide/Carbon)Overrides the respawn cooldown on sleeping bags and beds to a fixed number of seconds, with a separate shorter cooldown for a VIP permission. Uses OnEntitySpawned, OnPlayerRespawned and a startup sweep in OnServerInitialized.
- How to make a Rust sleeping bag teleport plugin (bag list, warmup, cooldown)Adds /bag, which lists the player's sleeping bags, beds and beach towels with grid references, and /bag <number> to teleport to one after a warmup timer with a per-player cooldown. It relies on the OnEntitySpawned and OnEntityKill hooks to keep an index of placed spawn points.
- How to make a Rust supply drop loot plugin (weighted custom table)Rewrites the contents of every falling supply drop with a weighted item table from the config and announces the drop position as a map grid reference. Uses OnEntitySpawned, a short timer to wait for the crate's inventory to exist, and GridReference on the crate position.
- How to make a Rust vehicle limit plugin (per player, per type)Caps how many vehicles each player can own per vehicle type, blocking purchases and entity spawns once the limit is reached and allowing higher caps through permissions. It uses OnEntitySpawned and OnEntityKill to count and track vehicles.
Build a plugin with OnEntitySpawned
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 OnEntitySpawned
Generate