Skip to content
RustPluginGenerator

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

AutoCodeLock.cs
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.

AutoCodeLock.json
{
  "Require permission": true,
  "Lock containers": true,
  "Consume code lock from inventory": true,
  "Codes": {}
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/autolock code <pin>Saves a 4-digit personal code used for all future automatic locks.autocodelock.use
/autolock clearRemoves your saved personal code and disables automatic locking.autocodelock.use
/autolockShows whether you have a code set and how to change it.autocodelock.use
Permissions
PermissionGrants
autocodelock.useGrants access to /autolock and the automatic code lock on placement.

How the code works

  1. Persist one PIN per player

    PluginConfig holds only RequirePermission, LockContainers and ConsumeCodeLock. The saved PINs live in a small data class written through Interface.Oxide.DataFileSystem.WriteObject("AutoCodeLock", data) in Unload and read in OnServerInitialized. Keys are UserIDString values, so the code survives restarts; a failed read is caught and replaced with an empty dictionary.

  2. Set the code with /autolock

    The [ChatCommand("autolock")] handler parses subcommands from args. code <pin> validates that the argument is four digits with int.TryParse and a length check, stores it in the dictionary and replies through lang. clear removes the entry. A bare /autolock reports the current state without revealing the PIN. Every branch checks autocodelock.use first with permission.UserHasPermission, though the reply in that case is the NoCode message.

  3. Catch placement in OnEntityBuilt

    OnEntityBuilt(Planner planner, UnityEngine.GameObject gameObject) fires after a deployable or building piece is placed. Read gameObject.ToBaseEntity() and null-check it. Obtain the placer via planner?.GetOwnerPlayer() (the planner is the held item). Skip NPCs, players without permission when RequirePermission is on, and players with no saved PIN.

  4. Filter to doors and containers

    Only Door entities and, when LockContainers is enabled, StorageContainer entities get a lock. Check the entity with is casts 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.

  5. Consume a code lock and mount it

    FindCodeLock(player) scans player.inventory.containerMain, containerBelt and containerWear for an item whose info.shortname is lock.code. If ConsumeCodeLock is true and no lock item is found, the player gets a NoLock message and the entity is left alone. Otherwise the plugin creates the lock with GameManager.server.CreateEntity("assets/prefabs/locks/keypad/lock.code.prefab", entity.transform.position, entity.transform.rotation), sets code and OwnerID, calls Spawn() and SetParent(entity). If ConsumeCodeLock is true and a lock item was found, that item is removed with item.Remove() and its container marked dirty.

  6. Authorize the owner

    After spawning, the plugin adds player.userID.Get() to codeLock.whitelistPlayers and calls SendNetworkUpdate. 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

  1. Save AutoCodeLock.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. 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.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/AutoCodeLock.json (Carbon: carbon/configs/AutoCodeLock.json). After editing, reload it:oxide.reload AutoCodeLock c.reload AutoCodeLock # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip: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 version
  • No inventory required

    Spawn the code lock for free instead of consuming one.

    Generate this version
  • Random 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.

Prompt: Rust Oxide plugin AutoCodeLock: /autolock code <pin> saves a player PIN, placing a door or box attaches a code lock using it, consuming a code lock from inventory, with a RequirePermission toggle.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize auto code lock

Customize