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.
object OnConstructionPlace(BaseEntity entity, Construction component, Construction.Target placement, BasePlayer ownerPlayer)Parameters
| Parameter | Type | Description |
|---|---|---|
entity | BaseEntity | The 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. |
component | Construction | The Construction component on the building planner that produced the entity. Use it if you need the PlacementType or other planner-side data. |
placement | Construction.Target | The Construction.Target describing where and how the item was placed (position, rotation, socket, and the entity being built on). |
ownerPlayer | BasePlayer | The 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
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.positionor 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
DoPlacementwithout a normal player context, soownerPlayercan be null. Always null-check before readingUserIDStringor permissions.Not a pre-build check
Use
CanBuildorCanAffordToPlacewhen you want to block placement before items are consumed.OnConstructionPlaceis 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.
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