Skip to content
RustPluginGenerator

How to make a Rust tool cupboard auth limit plugin (with VIP tiers)

Caps the number of authorized players on each tool cupboard and blocks extra authorizations with a chat message, using OnCupboardAuthorize. Players with a VIP permission get a higher cap.

By default a tool cupboard (BuildingPrivlidge) accepts an unlimited number of authorized players, which on busy servers turns one base into a shared clan storage and makes it hard to tell who actually holds build rights. This plugin puts a hard cap on the authorized list per cupboard and rejects any authorization attempt past that cap.

The cap comes from config: a base limit for everyone and a separate limit for players holding a VIP permission. The check happens in OnCupboardAuthorize, which Oxide fires right before a player is added to a cupboard's authorization list. Returning false blocks the action, so the plugin returns false with a message when the cupboard is already full. The same hook is also written for VehiclePrivilege so vehicle locks have their own limit from a separate config key. The plugin only counts existing entries and lets Rust handle the list itself, so authorizing, clearing and deauthorizing through the normal UI keep working.

The plugin code

CupboardAuthLimit.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("Cupboard Auth Limit", "RustPluginGenerator", "1.0.0")]
    [Description("Caps the number of players authorized on tool cupboards and vehicle privileges, with a VIP tier.")]
    public class CupboardAuthLimit : RustPlugin
    {
        private const string PermVip = "cupboardauthlimit.vip";
        private const string PermUse = "cupboardauthlimit.use";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Base auth limit")]
            public int BaseLimit { get; set; } = 4;

            [JsonProperty("VIP auth limit")]
            public int VipLimit { get; set; } = 8;

            [JsonProperty("Vehicle privilege limit")]
            public int VehicleLimit { get; set; } = 2;

            [JsonProperty("Bypass for admins")]
            public bool AdminBypass { get; set; } = 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"] = "This tool cupboard is full ({0} authorized players maximum).",
                ["VehicleLimitReached"] = "This vehicle lock is full ({0} authorized players maximum).",
                ["Status"] = "Base limit: {0} | VIP limit: {1} | Your limit: {2} | Admin bypass: {3}",
                ["NoPermission"] = "You do not have permission to use this command.",
                ["Usage"] = "Usage: /cbauthlimit status"
            }, this);
        }

        private void Init()
        {
            permission.RegisterPermission(PermVip, this);
            permission.RegisterPermission(PermUse, this);
        }

        private string Msg(string key, string userId = null, params object[] args)
            => string.Format(lang.GetMessage(key, this, userId), args);

        private int GetLimit(BasePlayer player)
        {
            if (permission.UserHasPermission(player.UserIDString, PermVip))
                return config.VipLimit;
            return config.BaseLimit;
        }

        private int CountAuth(BuildingPrivlidge priv, BasePlayer player)
        {
            int count = 0;
            foreach (var entry in priv.authorizedPlayers)
            {
                if (player != null && entry == player.userID.Get())
                    continue; // re-authorizing an existing member is fine
                count++;
            }
            return count;
        }

        private int CountAuth(VehiclePrivilege priv, BasePlayer player)
        {
            int count = 0;
            foreach (var entry in priv.authorizedPlayers)
            {
                if (player != null && entry == player.userID.Get())
                    continue;
                count++;
            }
            return count;
        }

        private object OnCupboardAuthorize(BuildingPrivlidge buildingPrivlidge, BasePlayer player)
        {
            if (buildingPrivlidge == null || player == null || player.IsNpc) return null;
            if (config.AdminBypass && player.IsAdmin) return null;

            int limit = GetLimit(player);
            if (limit <= 0) return null;

            int current = CountAuth(buildingPrivlidge, player);
            if (current >= limit)
            {
                player.ChatMessage(Msg("LimitReached", player.UserIDString, limit));
                return false;
            }
            return null;
        }

        private object OnCupboardAuthorize(VehiclePrivilege vehiclePrivilege, BasePlayer player)
        {
            if (vehiclePrivilege == null || player == null || player.IsNpc) return null;
            if (config.AdminBypass && player.IsAdmin) return null;
            if (config.VehicleLimit <= 0) return null;

            int current = CountAuth(vehiclePrivilege, player);
            if (current >= config.VehicleLimit)
            {
                player.ChatMessage(Msg("VehicleLimitReached", player.UserIDString, config.VehicleLimit));
                return false;
            }
            return null;
        }

        [ChatCommand("cbauthlimit")]
        private void CmdAuthLimit(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }
            if (args == null || args.Length == 0 || args[0].ToLowerInvariant() != "status")
            {
                player.ChatMessage(Msg("Usage", player.UserIDString));
                return;
            }

            player.ChatMessage(Msg("Status", player.UserIDString,
                config.BaseLimit, config.VipLimit, GetLimit(player), config.AdminBypass));
        }

        private void Unload()
        {
            config = null;
        }
    }
}

Hooks used: OnCupboardAuthorize

Config file

Created automatically on first load at oxide/config/CupboardAuthLimit.json.

CupboardAuthLimit.json
{
  "Base auth limit": 4,
  "VIP auth limit": 8,
  "Vehicle privilege limit": 2,
  "Bypass for admins": true
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/cbauthlimit statusShows the base limit, the VIP limit, the caller's effective limit and whether admin bypass is on.cupboardauthlimit.use
Permissions
PermissionGrants
cupboardauthlimit.vipRaises the authorization cap on tool cupboards to the VIP limit.
cupboardauthlimit.useAllows the /cbauthlimit status chat command.

How the code works

  1. Registering permissions and loading the config

    LoadDefaultConfig creates a PluginConfig with the [JsonProperty] keys Base auth limit, VIP auth limit, Vehicle privilege limit and Bypass for admins. LoadConfig reads the file, falls back to defaults and warns if deserialization returns null, then writes the file back. Init registers cupboardauthlimit.vip and cupboardauthlimit.use so VIP checks and the status command work as soon as the plugin loads.

  2. Counting current authorizations

    CountAuth walks authorizedPlayers on the cupboard and counts each entry, skipping the entry that matches the player being authorized (entry == player.userID.Get()). That skip means re-authorizing someone who is already on the list never gets blocked. The same method is written for BuildingPrivlidge and VehiclePrivilege. If your Rust build exposes the list differently, the compiler will point at the field name.

  3. Blocking extra authorizations in OnCupboardAuthorize

    The hook receives the BuildingPrivlidge and the BasePlayer. It null-checks both, ignores NPCs and returns null for admins when Bypass for admins is true. GetLimit returns the VIP value when permission.UserHasPermission matches cupboardauthlimit.vip, otherwise the base value. A configured limit of 0 or less means unlimited. When the count is at or above the limit, the player gets the LimitReached message and the hook returns false, which cancels the addition; otherwise it returns null and Rust proceeds normally.

  4. Handling vehicle privileges with the same hook

    OnCupboardAuthorize(VehiclePrivilege vehiclePrivilege, BasePlayer player) fires for the vehicle lock entity, which keeps its own authorizedPlayers list. This overload uses the Vehicle privilege limit config key instead of the base/VIP values, so vehicle locks get one flat cap. Setting that key to 0 or less disables the vehicle check entirely. The same admin bypass and NPC checks apply, and the blocked player sees the VehicleLimitReached message.

  5. Sending feedback through lang

    LoadDefaultMessages registers LimitReached and VehicleLimitReached, each with {0} for the limit value, plus Status, NoPermission and Usage. The Msg helper formats lang.GetMessage(key, this, userId), so the text can be edited in oxide/lang/en/CupboardAuthLimit.json without touching code. Telling the player the exact cap avoids support tickets about why authorization did nothing.

  6. The /cbauthlimit status command

    [ChatCommand("cbauthlimit")] handles the status subcommand and checks cupboardauthlimit.use first. It replies with the base limit, VIP limit, the caller's own effective limit from GetLimit and the current admin bypass setting, so staff can confirm a VIP group is wired to the permission. Anything else prints the usage line. The plugin uses no timers or data files, so Unload only clears the config reference.

Install it on your server

  1. Save CupboardAuthLimit.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/CupboardAuthLimit.json (Carbon: carbon/configs/CupboardAuthLimit.json). After editing, reload it:oxide.reload CupboardAuthLimit c.reload CupboardAuthLimit # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default cupboardauthlimit.vip c.grant group default cupboardauthlimit.vip # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

  • Team-sized limits with auto tiers

    Instead of two fixed limits, set the cap per player tier using the server's permission groups.

    Generate this version
  • Warn before the cupboard is full

    Tell the owner when the cupboard approaches the cap.

    Generate this version
  • Reset the list on wipe

    Clear all authorizations on new save for a fresh wipe start.

    Generate this version

FAQ

Does returning false in OnCupboardAuthorize actually stop the player from being added?

Yes. OnCupboardAuthorize is a hook that can cancel the action; returning false prevents the player from being authorized, and returning null lets Rust proceed. Pair the cancel with the LimitReached chat message so the player knows why nothing happened.

Will the limit apply to players already authorized before the plugin was installed?

Existing entries are not removed. The count includes everyone currently on the list, so a cupboard that already has more members than the limit simply blocks any new authorization until enough people are deauthorized. Clear cupboards manually before the first wipe if you want a clean start.

How do I give a group the higher limit?

Grant cupboardauthlimit.vip to the group with oxide.grant group vip cupboardauthlimit.vip. GetLimit checks permission.UserHasPermission(player.UserIDString, ...), which resolves group permissions, so team members in that group get the VIP auth limit value automatically.

Does this work on Carbon?

Yes. Carbon loads Oxide plugins, so drop CupboardAuthLimit.cs into the plugins folder and both hooks dispatch normally. Only the standard Oxide API is used: permission, lang, Config and the OnCupboardAuthorize hooks.

What happens to vehicle locks?

A second OnCupboardAuthorize overload handles VehiclePrivilege with the Vehicle privilege limit key. Set that key to 0 to leave vehicle locks unrestricted. This keeps modular car locks from becoming a loophole around the cupboard cap, while the base and VIP limits still apply to tool cupboards only.

Customize it for your server

Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.

Prompt: Create an Oxide Rust plugin that limits tool cupboard authorizations, with a base limit of 4, a VIP limit of 8 via the cupboardauthlimit.vip permission, a vehicle privilege limit of 2, and an admin bypass, using OnCupboardAuthorize.
Customize for my server

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

Last updated Oct 26, 2026.

Customize tool cupboard auth limit

Customize