How to make a Rust auto code lock plugin (personal codes)
Automatically attaches a code lock with the player's saved personal code to every door or box they place, consuming a code lock from their inventory. Uses OnEntityBuilt plus a stored per-player PIN.
Manually locking every door and box after a build is repetitive and easy to forget, which is how bases get raided through an unlocked door. This plugin reads a per-player PIN once (set with /autolock code <pin>), stores it in oxide/data, and then watches placement. When a player places a door or a deployable container, the plugin looks for a code lock in their inventory, optionally removes one, spawns a CodeLock on the entity, sets the code, and whitelists the owner. If the player has no code set, nothing happens silently; if they have a code but no lock in inventory and ConsumeCodeLock is on, they get a chat message and the entity stays unlocked.
The plugin code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("AutoCodeLock", "RustPluginGenerator", "1.0.0")]
[Description("Attaches a code lock with the player's saved personal code to doors and boxes they place.")]
public class AutoCodeLock : RustPlugin
{
private const string PermUse = "autocodelock.use";
private const string DataFile = "AutoCodeLock";
private const string CodeLockPrefab = "assets/prefabs/locks/keypad/lock.code.prefab";
private PluginConfig config;
private StoredData data;
private class PluginConfig
{
[JsonProperty("Require permission")]
public bool RequirePermission = true;
[JsonProperty("Lock containers")]
public bool LockContainers = true;
[JsonProperty("Consume code lock from inventory")]
public bool ConsumeCodeLock = true;
}
private class StoredData
{
[JsonProperty("Codes")]
public Dictionary<string, string> Codes = new Dictionary<string, string>();
}
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>
{
["Usage"] = "Usage: /autolock code <pin> or /autolock clear",
["CodeSet"] = "Your personal code is set to {0}. Doors and boxes you place will lock automatically.",
["CodeCleared"] = "Your personal code has been removed.",
["NoCode"] = "You do not have a personal code set. Use /autolock code <pin>.",
["HaveCode"] = "You have a personal code set ({0}). Use /autolock clear to remove it.",
["InvalidPin"] = "The code must be exactly 4 digits.",
["Locked"] = "Your personal code lock has been attached.",
["NoLock"] = "No code lock found in your inventory, the door was left unlocked."
}, 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(PermUse, this);
}
private void OnServerInitialized()
{
try
{
data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(DataFile);
}
catch
{
data = null;
}
if (data == null) data = new StoredData();
if (data.Codes == null) data.Codes = new Dictionary<string, string>();
}
private void Unload()
{
Interface.Oxide.DataFileSystem.WriteObject(DataFile, data);
}
private bool CanUse(BasePlayer player)
{
if (player == null || player.IsNpc) return false;
if (config.RequirePermission && !permission.UserHasPermission(player.UserIDString, PermUse)) return false;
return true;
}
[ChatCommand("autolock")]
private void CmdAutoLock(BasePlayer player, string command, string[] args)
{
if (player == null || player.IsNpc) return;
if (config.RequirePermission && !permission.UserHasPermission(player.UserIDString, PermUse))
{
SendReply(player, Msg("NoCode", player.UserIDString));
return;
}
if (args == null || args.Length == 0)
{
string existing;
if (data.Codes.TryGetValue(player.UserIDString, out existing))
SendReply(player, Msg("HaveCode", player.UserIDString, existing));
else
SendReply(player, Msg("Usage", player.UserIDString));
return;
}
string action = args[0].ToLower();
if (action == "code")
{
if (args.Length < 2 || args[1].Length != 4 || !int.TryParse(args[1], out int _))
{
SendReply(player, Msg("InvalidPin", player.UserIDString));
return;
}
data.Codes[player.UserIDString] = args[1];
SendReply(player, Msg("CodeSet", player.UserIDString, args[1]));
return;
}
if (action == "clear")
{
data.Codes.Remove(player.UserIDString);
SendReply(player, Msg("CodeCleared", player.UserIDString));
return;
}
SendReply(player, Msg("Usage", player.UserIDString));
}
private void OnEntityBuilt(Planner planner, UnityEngine.GameObject gameObject)
{
if (gameObject == null) return;
BaseEntity entity = gameObject.ToBaseEntity();
if (entity == null) return;
bool isDoor = entity is Door;
bool isContainer = config.LockContainers && entity is StorageContainer;
if (!isDoor && !isContainer) return;
if (planner == null) return;
BasePlayer player = planner.GetOwnerPlayer();
if (!CanUse(player)) return;
string pin;
if (!data.Codes.TryGetValue(player.UserIDString, out pin) || string.IsNullOrEmpty(pin)) return;
Item lockItem = FindCodeLock(player);
if (lockItem == null && config.ConsumeCodeLock)
{
SendReply(player, Msg("NoLock", player.UserIDString));
return;
}
CodeLock codeLock = GameManager.server.CreateEntity(CodeLockPrefab, entity.transform.position, entity.transform.rotation) as CodeLock;
if (codeLock == null) return;
codeLock.OwnerID = player.userID.Get();
codeLock.code = pin;
codeLock.Spawn();
codeLock.SetParent(entity, true, true);
codeLock.whitelistPlayers.Add(player.userID.Get());
codeLock.SendNetworkUpdate();
if (config.ConsumeCodeLock && lockItem != null)
{
ItemContainer parent = lockItem.parent;
lockItem.Remove();
if (parent != null) parent.MarkDirty();
}
SendReply(player, Msg("Locked", player.UserIDString));
}
private Item FindCodeLock(BasePlayer player)
{
return FindIn(player.inventory.containerMain) ?? FindIn(player.inventory.containerBelt) ?? FindIn(player.inventory.containerWear);
}
private Item FindIn(ItemContainer container)
{
if (container == null) return null;
foreach (Item item in container.itemList)
{
if (item?.info != null && item.info.shortname == "lock.code") return item;
}
return null;
}
}
}Hooks used: OnServerInitialized, OnEntityBuilt
Config file
Created automatically on first load at oxide/config/AutoCodeLock.json.
{
"Require permission": true,
"Lock containers": true,
"Consume code lock from inventory": true,
"Codes": {}
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/autolock code <pin> | Saves a 4-digit personal code used for all future automatic locks. | autocodelock.use |
/autolock clear | Removes your saved personal code and disables automatic locking. | autocodelock.use |
/autolock | Shows whether you have a code set and how to change it. | autocodelock.use |
| Permission | Grants |
|---|---|
autocodelock.use | Grants access to /autolock and the automatic code lock on placement. |
How the code works
Persist one PIN per player
PluginConfigholds onlyRequirePermission,LockContainersandConsumeCodeLock. The saved PINs live in a small data class written throughInterface.Oxide.DataFileSystem.WriteObject("AutoCodeLock", data)inUnloadand read inOnServerInitialized. Keys areUserIDStringvalues, so the code survives restarts; a failed read is caught and replaced with an empty dictionary.Set the code with /autolock
The
[ChatCommand("autolock")]handler parses subcommands fromargs.code <pin>validates that the argument is four digits withint.TryParseand a length check, stores it in the dictionary and replies through lang.clearremoves the entry. A bare/autolockreports the current state without revealing the PIN. Every branch checksautocodelock.usefirst withpermission.UserHasPermission, though the reply in that case is theNoCodemessage.Catch placement in OnEntityBuilt
OnEntityBuilt(Planner planner, UnityEngine.GameObject gameObject)fires after a deployable or building piece is placed. ReadgameObject.ToBaseEntity()and null-check it. Obtain the placer viaplanner?.GetOwnerPlayer()(the planner is the held item). Skip NPCs, players without permission whenRequirePermissionis on, and players with no saved PIN.Filter to doors and containers
Only
Doorentities and, whenLockContainersis enabled,StorageContainerentities get a lock. Check the entity withiscasts after the null guard. Other deployables simply return. This keeps boxes and doors covered without touching furnaces, turrets or walls, and keeps the hook cheap because filtering happens before any inventory work.Consume a code lock and mount it
FindCodeLock(player)scansplayer.inventory.containerMain,containerBeltandcontainerWearfor an item whoseinfo.shortnameislock.code. IfConsumeCodeLockis true and no lock item is found, the player gets aNoLockmessage and the entity is left alone. Otherwise the plugin creates the lock withGameManager.server.CreateEntity("assets/prefabs/locks/keypad/lock.code.prefab", entity.transform.position, entity.transform.rotation), setscodeandOwnerID, callsSpawn()andSetParent(entity). IfConsumeCodeLockis true and a lock item was found, that item is removed withitem.Remove()and its container marked dirty.Authorize the owner
After spawning, the plugin adds
player.userID.Get()tocodeLock.whitelistPlayersand callsSendNetworkUpdate. No locked flag is set; the owner is whitelisted so they can open the door without typing the PIN, while other players must enter it. A lang message confirms the lock.
Install it on your server
Save AutoCodeLock.cs
Keep the file name exactly as the class name. Oxide matches them.Upload it to the plugins folder
Oxide:oxide/plugins/. Carbon:carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.Edit the config if you want
On first load the default config is written tooxide/config/AutoCodeLock.json(Carbon:carbon/configs/AutoCodeLock.json). After editing, reload it:oxide.reload AutoCodeLock c.reload AutoCodeLock # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default autocodelock.use c.grant group default autocodelock.use # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Shared team codes
Use one code for an entire team so members can open each other's doors.
Generate this versionNo inventory required
Spawn the code lock for free instead of consuming one.
Generate this versionRandom code per placement
Generate a fresh random PIN for each door and tell the owner in chat.
Generate this version
FAQ
Does this work on both Oxide and Carbon?
Yes. Drop the .cs file in oxide/plugins and reload the server or run oxide.reload AutoCodeLock. Carbon loads Oxide plugins from the same folder. No other dependencies are needed.
What happens if a player has no code set?
The entity is placed without a lock and the player is not charged a code lock. Run /autolock code <pin> once and future doors and boxes are locked automatically.
Can the player change the code afterwards?
Yes. The lock is a normal code lock. The owner is whitelisted, so they can open it and change or remove the code like any other lock. The saved PIN is only used at placement time.
Is the code readable by admins?
The PIN is stored in plain text in oxide/data/AutoCodeLock.json. Keep that file out of shared access, since anyone with file access can read all saved codes.
Why was no lock applied to my box?
Check that LockContainers is true in the config and that autocodelock.use is granted if RequirePermission is on. Boxes only receive locks when the container setting is enabled.
Customize it for your server
Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More building plugins
- Auto closing doorsCloses doors a configurable number of seconds after a player opens them, with a per-player /autodoor toggle and a personal delay clamped to serverโฆOnDoorOpened
- Auto upgrade on buildLets each player pick a building grade with /up 1-4, then upgrades every building block they place to that grade using resources taken from theirโฆOnServerInitializedOnEntityBuilt
- Building grade limitsRestricts building upgrades to a configurable maximum grade, such as stopping at stone or metal, with a permission that lifts the cap.OnStructureUpgrade
Last updated Oct 26, 2026.
Customize auto code lock
Customize