Skip to content
RustPluginGenerator

OnConstructionPlace

OnConstructionPlace is called from Planner.DoPlacement right after a construction entity has been created from the building planner. Returning a non-null value cancels the placement and removes the entity.

Signature
object OnConstructionPlace(BaseEntity entity, Construction component, Construction.Target placement, BasePlayer ownerPlayer)

Parameters

Hook parameters
ParameterTypeDescription
entityBaseEntityThe BaseEntity that was just created by the construction, e.g. a BuildingBlock, Door, or deployable. It already exists in the world at this point, which is why it must be killed when you cancel the placement.
componentConstructionThe Construction component on the building planner that produced the entity. Use it if you need the PlacementType or other planner-side data.
placementConstruction.TargetThe Construction.Target describing where and how the item was placed (position, rotation, socket, and the entity being built on).
ownerPlayerBasePlayerThe BasePlayer whose planner is doing the placement. Always check for null since this can be called outside a normal player-controlled placement path.

Return value

Return null (or nothing) to allow the placement to stand. Return any non-null value to cancel it: Oxide then calls KillMessage() on the freshly created entity, removing it from the world. The planner's original return value is discarded, so returning a GameObject here does not become the method's result.

How it works

This hook fires inside Planner.DoPlacement immediately after the game has created the construction entity from the player's building plan. The server has already run CreateConstruction, so a real entity exists before your hook is called. That means anything you do here happens after the entity was spawned, and if you cancel the placement the game kills the entity rather than preventing it from being made.

The entity parameter is the placed object (a BuildingBlock, a door, a deployable, etc.). component is the Construction component of the planner. placement carries the target position, rotation and socket data. ownerPlayer is the player doing the placement and can be null in non-standard paths, so guard it.

A common misconception is that returning a value here "rejects" the build before it happens. In practice the entity is created first; a non-null return makes Oxide call KillMessage() on it, which refunds nothing and may race with your own logic. If you need to block placement entirely, prefer CanBuild or CanAffordToPlace. Use OnConstructionPlace for post-placement reactions, per-player build limits, or to add logging and side effects tied to a specific entity type.

Example plugin: Limit how many foundations each player can place

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

namespace Oxide.Plugins
{
    [Info("Foundation Limit", "RustPluginGenerator", "1.0.0")]
    [Description("Caps the number of foundations a single player can place.")]
    public class FoundationLimit : RustPlugin
    {
        private const string PermBypass = "foundationlimit.bypass";
        private const string FoundationShortname = "foundation";

        private PluginConfig config;
        private readonly Dictionary<ulong, int> placed = new Dictionary<ulong, int>();

        private class PluginConfig
        {
            [JsonProperty("Maximum foundations per player")]
            public int MaxFoundations = 10;

            [JsonProperty("Bypass permission")]
            public bool UseBypassPermission = 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 have reached the foundation limit ({0}).",
                ["Reset"] = "Your foundation count has been reset."
            }, 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 object OnConstructionPlace(BaseEntity entity, Construction component, Construction.Target placement, BasePlayer ownerPlayer)
        {
            if (entity == null || ownerPlayer == null || ownerPlayer.IsNpc) return null;

            var block = entity as BuildingBlock;
            if (block == null) return null;
            if (block.ShortPrefabName == null || !block.ShortPrefabName.Contains(FoundationShortname)) return null;

            ulong userId = ownerPlayer.userID.Get();

            if (config.UseBypassPermission && permission.UserHasPermission(ownerPlayer.UserIDString, PermBypass))
                return null;

            int current;
            placed.TryGetValue(userId, out current);

            if (current >= config.MaxFoundations)
            {
                ownerPlayer.ChatMessage(Msg("LimitReached", ownerPlayer.UserIDString, config.MaxFoundations));
                return true;
            }

            placed[userId] = current + 1;
            return null;
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (player == null) return;
            placed.Remove(player.userID.Get());
        }

        private void Unload()
        {
            placed.Clear();
        }
    }
}

The plugin caps the number of foundations a single player can place within one building session. It uses OnConstructionPlace to inspect the created entity: if it is a foundation BuildingBlock and the owner is over the configured limit, it returns true to make Oxide kill the entity so the placement fails. CanBuild would be a cleaner place to block earlier, but this example shows the hook in action. Counts are tracked per player and reset on disconnect. The config exposes the maximum foundations per player and is validated on load.

Common use cases

  • Per-player build limits

    Enforce a maximum number of a specific structure type (foundations, high walls, turrets) by counting or inspecting entities as they are placed and cancelling when the cap is exceeded.

  • Zone-based placement rules

    Check placement.position or the entity's transform against a no-build zone and return a value to remove builds that slipped through other checks.

  • Audit logging of placements

    Record which player placed which prefab where for admin review, since the entity and target are both available at this point.

Pitfalls

  • Entity already exists

    The entity is created before the hook runs, so cancelling only kills it afterwards. No items are refunded automatically - if you want to refund the player, handle that yourself.

  • null ownerPlayer

    Some placement paths call DoPlacement without a normal player context, so ownerPlayer can be null. Always null-check before reading UserIDString or permissions.

  • Not a pre-build check

    Use CanBuild or CanAffordToPlace when you want to block placement before items are consumed. OnConstructionPlace is a post-creation reaction, not a pre-placement veto.

Build a plugin with OnConstructionPlace

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

Prompt: Per-player build limits using the OnConstructionPlace 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 OnConstructionPlace

Generate