CanBuild
CanBuild is called from Planner.DoBuild right after the client's build request has been decoded into a Construction.Target, before the planner decides on parenting and placement. Returning a non-null value aborts the build.
object CanBuild(Planner planner, Construction construction, Construction.Target target)Parameters
| Parameter | Type | Description |
|---|---|---|
planner | Planner | The Planner entity (building plan) that the player is holding when the placement request arrives. Use it to read OwnerID, the active item, or its position. |
construction | Construction | The Construction component describing what is being built: construction grade, prefab, the deployable or building block being placed. |
target | Construction.Target | The Construction.Target holding the resolved placement data: entity, position, rotation, normal, socket, snapping state, and the valid flag set just before the hook fires. |
Return value
Returning null lets the original Planner.DoBuild continue with parenting and placement. Returning any non-null object makes the patched method return immediately, cancelling the placement for this request; the planner never reaches its ShouldParent logic, so no entity is spawned and no resources are consumed.
How it works
CanBuild fires once per build attempt, when a player sends a build request through a Planner. By the time it runs, the server has already copied the client's socket, snapping mode, snap position and snap rotation into target and set target.valid to true; the hook is checked before ShouldParent and the rest of the placement logic runs.
Use the parameters to inspect the whole request:
planneris the building plan entity;planner.GetOwnerPlayer()gives the builder (may be null for edge cases).constructionis the planned structure component;construction.grade,construction.currentGradeandconstruction.canRotatedescribe the block.targetcarriesentity,position,rotation,normal,socket,snappingMode,isSnapped,snappedPosition,snappedRotationandvalid;target.entityis the prefab that would be spawned.
Narrow the signature by declaring Planner, Construction or Construction.Target only if you need a specific subtype; otherwise keep the exact types. Returning non-null blocks only this one build attempt; the player is not notified, so send feedback yourself.
Example plugin: Block building while inside a no-build zone
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("CanBuild Zone", "RustPluginGenerator", "1.0.0")]
[Description("Blocks structure placement inside configured no-build zones.")]
public class CanBuildZone : RustPlugin
{
private const string PermBypass = "canbuildzone.bypass";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Zones")]
public List<Zone> Zones = new List<Zone>();
[JsonProperty("Zone message radius")]
public float MessageRadius = 0f;
}
private class Zone
{
[JsonProperty("Name")]
public string Name = "spawn";
[JsonProperty("Center X")]
public float X = 0f;
[JsonProperty("Center Y")]
public float Y = 0f;
[JsonProperty("Center Z")]
public float Z = 0f;
[JsonProperty("Radius")]
public float Radius = 100f;
[JsonProperty("Allowed Team ID (0 = none)")]
public ulong AllowedTeam = 0;
}
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>
{
["NoBuildZone"] = "You cannot build here ({0})."
}, 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 CanBuild(Planner planner, Construction construction, Construction.Target target)
{
if (planner == null) return null;
if (config.Zones == null || config.Zones.Count == 0) return null;
var player = planner.GetOwnerPlayer();
if (player == null) return null;
if (player.IsAdmin || permission.UserHasPermission(player.UserIDString, PermBypass)) return null;
Vector3 position = target.position;
for (int i = 0; i < config.Zones.Count; i++)
{
Zone zone = config.Zones[i];
if (zone == null || zone.Radius <= 0f) continue;
var center = new Vector3(zone.X, zone.Y, zone.Z);
if (Vector3.Distance(position, center) > zone.Radius) continue;
if (zone.AllowedTeam != 0 && player.currentTeam == zone.AllowedTeam) continue;
player.ChatMessage(Msg("NoBuildZone", player.UserIDString, zone.Name));
return true;
}
return null;
}
}
}The plugin defines a configurable list of no-build zones (center point, radius, optional allowed team ID). In the CanBuild hook the planner's owner player is resolved; for each zone, the target build position (target.position) is compared with the zone center. Zones with radius <= 0 are ignored. If the position is within the radius and the player is not on the zone's allowed team, the hook returns a non-null value to abort the placement and the player gets a chat notice through the language file. Admins and players with the canbuildzone.bypass permission skip the check.
Common use cases
No-build zones around spawn
Block placement near spawn, event areas or monuments and inform the player via chat.
Building restrictions per team
Deny placement when the planner owner is in a team with a raid in progress, or for a specific team ID that the config lists.
Height and terrain restrictions
Reject structures placed above a Y threshold, below sea level or inside a no-PvP region controlled by your own data files.
Pitfalls
Null planner owner
planner and planner.GetOwnerPlayer() can be null in some code paths; always null-check before reading OwnerID or sending a message.
No automatic feedback
Returning a value silently cancels the placement. Send a chat or CUI message yourself, or the player assumes the server is laggy.
Hook fires for every block
CanBuild runs on each build request, including spam. Keep the callback cheap: no file I/O, no per-call logging, no heavy LINQ over the world.
Plugin recipes that use CanBuild
- How to make a Rust deployable limits plugin (per-prefab caps with VIP tiers)Caps how many of each deployable (by short prefab name) a player can own, with per-permission-group limits, using OnEntityBuilt to enforce, CanBuild to block placement early, and OnEntityKill plus a startup scan of BaseNetworkable.serverEntities to keep counts accurate.
- How to make a Rust monument build-block plugin (radius zones)Blocks building placement inside a configurable radius of any listed monument, with per-monument radius overrides, using the CanBuild hook and a chat command that reports the nearest monument. The denial message names the monument that caused the block.
Build a plugin with CanBuild
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 CanBuild
Generate