Skip to content
RustPluginGenerator

OnItemDeployed

OnItemDeployed is called after a deployable item has been spawned into the world, right after the mod's OnDeployed callback and before the item amount is consumed. It fires for both regular deployments and slot-based placements such as wall-mounted items.

Signature
void OnItemDeployed(Deployer deployer, ItemModDeployable modDeployable, BaseEntity entity)

Parameters

Hook parameters
ParameterTypeDescription
deployerDeployerThe Deployer component that performed the placement, usually parented to the item being deployed. It gives access to the owning item and player through its normal members.
modDeployableItemModDeployableThe ItemModDeployable on the item that was used. Its entityPrefab, and the item definition behind it, describe which entity the item spawns.
entityBaseEntityThe BaseEntity that was just spawned on the server. This is the placed object (a deployable, a slot-based entity, or another prefab) and is safe to inspect, modify or kill.

Return value

The return value is ignored on both injection sites; returning null, true or any other value does not change the deployment. The game continues with UseItemAmount(1) regardless.

How it works

The hook fires inside Deployer.DoDeploy_Regular and Deployer.DoDeploy_Slot, after the game has set the entity's skin, owner and position, called Spawn(), and invoked ItemModDeployable.OnDeployed. It runs before UseItemAmount(1) consumes the deployed item, so at call time the item still exists in the player's inventory.

entity is the placed object itself: use it to read OwnerID, skinID, PrefabName or its position, or to adjust or remove it. modDeployable identifies the item type that was used (its entityPrefab and item definition), and deployer is the component that ran the deployment.

Both sites call the same hook name but pass different argument types: the regular path passes (Deployer, ItemModDeployable, BaseEntity) and the slot path passes (Deployer, BaseEntity, BaseEntity). Declare the exact signature you need; Oxide dispatches by argument type, so a method declared with ItemModDeployable will not fire from the slot path. If you only care about the placed entity, a method taking a single BaseEntity is not enough here because the hook passes three arguments, so declare all three.

Because the hook fires per placement, treat it as low frequency but avoid PrintToChat or expensive work inside it.

Other signatures

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

  • void OnItemDeployed(Deployer deployer, BaseEntity entity, BaseEntity baseEntity2)

Example plugin: Limit deployables per player and block listed prefabs

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

namespace Oxide.Plugins
{
    [Info("Deploy Limit", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks configured deployables and caps how many of each prefab a player can place.")]
    public class DeployLimit : RustPlugin
    {
        private const string PermBypass = "deploylimit.bypass";

        private class PluginConfig
        {
            [JsonProperty("Blocked prefabs")]
            public List<string> BlockedPrefabs = new List<string> { "wall.external.high", "barricade.metal" };

            [JsonProperty("Default limit per player")]
            public int DefaultLimit = 5;

            [JsonProperty("Limit overrides by prefab")]
            public Dictionary<string, int> Limits = new Dictionary<string, int>
            {
                ["wall.external.high.stone"] = 10,
                ["door.hinged.metal"] = 3
            };
        }

        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);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Blocked"] = "Placing {0} is not allowed on this server.",
                ["Limit"] = "You already have {0} of {0} and cannot place more.",
                ["LimitReached"] = "You have reached the limit of {0} for {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);
        }

        private void OnItemDeployed(Deployer deployer, ItemModDeployable modDeployable, BaseEntity entity)
        {
            HandleDeployment(entity);
        }

        private void OnItemDeployed(Deployer deployer, BaseEntity entity, BaseEntity placed)
        {
            HandleDeployment(placed);
        }

        private void HandleDeployment(BaseEntity entity)
        {
            if (entity == null || entity.IsDestroyed) return;
            var player = entity.OwnerID == 0 ? null : BasePlayer.FindByID(entity.OwnerID);
            if (player == null) return;
            if (permission.UserHasPermission(player.UserIDString, PermBypass)) return;

            string prefab = entity.ShortPrefabName;
            string display = entity.ShortPrefabName;

            if (config.BlockedPrefabs != null && config.BlockedPrefabs.Contains(prefab))
            {
                entity.Kill();
                player.ChatMessage(Msg("Blocked", player.UserIDString, display));
                return;
            }

            int limit = GetLimit(prefab);
            if (limit <= 0) return;

            int count = CountOwned(player.userID.Get(), prefab);
            if (count > limit)
            {
                entity.Kill();
                player.ChatMessage(Msg("LimitReached", player.UserIDString, limit, display));
            }
        }

        private int GetLimit(string prefab)
        {
            int limit;
            if (config.Limits != null && config.Limits.TryGetValue(prefab, out limit)) return limit;
            return config.DefaultLimit;
        }

        private int CountOwned(ulong ownerId, string prefab)
        {
            int count = 0;
            foreach (var networkable in BaseNetworkable.serverEntities)
            {
                var entity = networkable as BaseEntity;
                if (entity == null || entity.IsDestroyed) continue;
                if (entity.OwnerID != ownerId) continue;
                if (entity.ShortPrefabName != prefab) continue;
                count++;
            }
            return count;
        }
    }
}

The plugin subscribes to OnItemDeployed(Deployer, ItemModDeployable, BaseEntity) and looks up the placed entity's prefab name. If the prefab is on the config blocklist, it destroys the entity with Kill() and sends the owner a localized message; the item is still consumed because the original code path continues, which the example notes to the player. Otherwise it counts how many live entities of the same prefab the owner already has and kills the new one when the per-player limit is reached. Configuration holds the blocklist, the default limit and an optional per-prefab override dictionary. The count walks BaseNetworkable.serverEntities, so it only runs on an actual deployment, not on a timer.

Common use cases

  • Block specific deployables

    Kill the spawned entity when its prefab appears in a config blocklist, for example to stop players placing certain items on PvE or event servers.

  • Cap deployables per player

    Count existing entities owned by the player with the same prefab and remove the new one once a limit is reached, keeping base spam under control.

  • Tag or modify the placed object

    Adjust properties on entity such as health, skin or flags right after spawning, or attach bookkeeping data keyed by entity.net.ID.Value.

Pitfalls

  • The item is still consumed

    The hook fires before UseItemAmount(1), and the return value is ignored, so killing the entity does not refund the item. Grant a replacement item yourself if you want to compensate the player.

  • Argument types differ between deploy paths

    The regular path passes ItemModDeployable as the second argument while the slot path passes a BaseEntity. A method declared with ItemModDeployable never fires from slot deployments; use the BaseEntity, BaseEntity overload if you need those.

  • Entity may already be gone

    Other plugins or the game can remove the entity in the same tick. Check entity == null || entity.IsDestroyed before touching it, and do not assume entity is a specific subclass on the slot path.

Plugin recipes that use OnItemDeployed

Build a plugin with OnItemDeployed

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

Prompt: Block specific deployables using the OnItemDeployed 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 OnItemDeployed

Generate