Skip to content
RustPluginGenerator

OnWorldPrefabSpawned

OnWorldPrefabSpawned is called after World.SpawnPrefab instantiates a world prefab (decor, roadside objects, biome props and similar map fills) and returns the resulting GameObject. It fires only for prefabs spawned through the World spawner, not for every entity in the game.

Signature
void OnWorldPrefabSpawned(UnityEngine.GameObject gameObject, string category)

Parameters

Hook parameters
ParameterTypeDescription
gameObjectUnityEngine.GameObjectThe UnityEngine.GameObject instance just produced by World.SpawnPrefab. Can be null if the prefab failed to spawn, so null-check before touching it.
categorystringThe world spawn category string the spawner used, for example a biome or prefab group identifier such as 'Roadside' or 'Powerline'. Lets you filter spawns by the region or system that requested them.

Return value

The return value is ignored. World.SpawnPrefab returns void and the hook is called purely for notification, so returning anything (including null) has no effect on the spawn or on later spawn timings.

How it works

The hook fires inside World.SpawnPrefab, immediately after the prefab is spawned and after the server has already recorded the spawn timing for that category. It runs once per successful prefab instantiation and passes both the new GameObject and the category string the spawner was working on. At this point the object exists in the scene, so the GameObject has its transform in place, but it is not exposed as a typed BaseEntity by the hook itself.

Because the callback only receives a GameObject, you have to locate the root networked entity yourself if you need one. The usual route is a GetComponent<BaseEntity>() lookup on the GameObject, or a GetComponentInParent/GetComponentInChildren depending on how the prefab is laid out. The category string is the cheap way to narrow the work: compare it against the categories you care about before doing any component lookups.

Declaring a more specific parameter type does not help here because the second parameter is a plain string and the first is a GameObject; narrowing only matters for typed entity hooks. This hook fires during world setup and relocation paths, so keep the body light and avoid chat output. Oxide dispatch still invokes the hook for every spawned prefab, including ones whose category you ignore.

Example plugin: Tag and track roadside barrel and crate spawns

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

namespace Oxide.Plugins
{
    [Info("World Spawn Tracker", "RustPluginGenerator", "1.0.0")]
    [Description("Counts world prefab spawns by category and reports them to admins")]
    public class WorldSpawnTracker : RustPlugin
    {
        private const string PermReport = "worldspawntracker.report";

        private PluginConfig config;
        private readonly Dictionary<string, int> counts = new Dictionary<string, int>();
        private int totalSpawns;

        private class PluginConfig
        {
            [JsonProperty("Categories to track (empty means all)")]
            public List<string> Categories { get; set; } = new List<string>();

            [JsonProperty("Save summary to data file on unload")]
            public bool SaveOnUnload { get; set; } = true;
        }

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

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

        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>
            {
                ["NoData"] = "No world prefab spawns recorded yet.",
                ["Summary"] = "Tracked {0} prefabs across {1} categories.",
                ["Category"] = "{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(PermReport, this);
        }

        private void OnWorldPrefabSpawned(GameObject gameObject, string category)
        {
            if (gameObject == null) return;
            if (string.IsNullOrEmpty(category)) return;
            if (config.Categories.Count > 0 && !config.Categories.Contains(category)) return;

            counts.TryGetValue(category, out int current);
            counts[category] = current + 1;
            totalSpawns++;
        }

        [ChatCommand("spawnreport")]
        private void CmdSpawnReport(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermReport))
            {
                SendReply(player, "You do not have permission to use this command.");
                return;
            }

            if (counts.Count == 0)
            {
                SendReply(player, Msg("NoData", player.UserIDString));
                return;
            }

            SendReply(player, Msg("Summary", player.UserIDString, totalSpawns, counts.Count));
            foreach (KeyValuePair<string, int> entry in counts)
                SendReply(player, Msg("Category", player.UserIDString, entry.Key, entry.Value));
        }

        private void Unload()
        {
            if (!config.SaveOnUnload || totalSpawns == 0) return;

            Interface.Oxide.DataFileSystem.WriteObject(Name, new TrackerData
            {
                Counts = new Dictionary<string, int>(counts),
                Total = totalSpawns
            });
        }
    }
}

The plugin listens for OnWorldPrefabSpawned and records how many prefabs appear under each world spawn category. It ignores null GameObjects and empty categories, then resolves the GameObject's BaseEntity to get a stable network id and short prefab name. A counter dictionary in memory tracks per-category totals, and an admin chat command reports them; a separate option writes the summary to oxide/data on unload. The config lets an admin restrict tracking to specific categories so map-wide spawn traffic does not bloat the data file, and it warns only at startup for invalid config.

Common use cases

  • Measure world spawn density per category

    Count prefabs by the category string to see which biome or world system produces the most clutter. Useful for choosing whether to strip decor before a wipe.

  • Filter prefabs for cleanup passes

    Collect network ids of spawned prefabs whose short prefab name matches a configurable blocklist, then kill them in a timer to thin out roads or fields. Doing the lookups lazily keeps the hook cheap.

  • Detect unexpected world spawns during a wipe

    Log the first prefab seen per category at world load so admins can confirm the map spawned the expected roadside and monument decorations.

Pitfalls

  • Not every entity comes through World.SpawnPrefab

    Player-placed deployables, loot spawned by monument logic and entities created by plugins usually bypass this spawner, so this hook is not a substitute for OnEntitySpawned.

  • The GameObject can be null

    If the prefab fails to instantiate, gameObject may be null. Always null-check before a GetComponent call to avoid a NullReferenceException in a hot path.

  • Hook body runs during world build

    This fires repeatedly while the map is being populated. Avoid chat output, data writes or expensive component searches per spawn; aggregate into a dictionary and flush on a timer or in Unload.

Build a plugin with OnWorldPrefabSpawned

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

Prompt: Measure world spawn density per category using the OnWorldPrefabSpawned 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 OnWorldPrefabSpawned

Generate