Skip to content
RustPluginGenerator

How to make a Rust auto close doors plugin (per-player delay)

Closes doors a configurable number of seconds after a player opens them, with a per-player /autodoor toggle and a personal delay clamped to server limits. It uses OnDoorOpened to start a timer for each door.

Doors left open are a quiet raiding problem: a player runs in, forgets the door, and anything can follow. You can solve it server-side by reacting to the moment a door opens and closing it again after a short delay. The tricky parts are knowing who opened it, not closing doors that are already shut or destroyed, and letting each player pick a delay that feels right without letting them set 0.1 seconds or 600. The plugin below handles all three. OnDoorOpened records the player, reads their saved settings, clamps their delay into the configured minimum and maximum, and schedules a timer that re-checks the door before closing it. Settings live in oxide/data/AutoDoors and are written when a player changes them and again on unload. A single /autodoor command toggles the feature and accepts an optional delay argument.

The plugin code

AutoDoors.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Auto Doors", "RustPluginGenerator", "1.0.0")]
    [Description("Closes doors automatically after a per-player configurable delay.")]
    public class AutoDoors : RustPlugin
    {
        private const string PermUse = "autodoors.use";
        private const string DataName = "AutoDoors";

        private PluginConfig config;
        private StoredData data;
        private readonly Dictionary<ulong, Timer> timers = new Dictionary<ulong, Timer>();

        private class PluginConfig
        {
            [JsonProperty("Default enabled")]
            public bool DefaultEnabled = true;

            [JsonProperty("Default delay (seconds)")]
            public float DefaultDelay = 8f;

            [JsonProperty("Minimum delay (seconds)")]
            public float MinimumDelay = 3f;

            [JsonProperty("Maximum delay (seconds)")]
            public float MaximumDelay = 30f;
        }

        private class PlayerSettings
        {
            [JsonProperty("Enabled")]
            public bool Enabled = true;

            [JsonProperty("Delay (seconds)")]
            public float Delay = 8f;
        }

        private class StoredData
        {
            [JsonProperty("Players")]
            public Dictionary<string, PlayerSettings> Players = new Dictionary<string, PlayerSettings>();
        }

        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();
            }

            if (config.MinimumDelay < 0.5f) config.MinimumDelay = 0.5f;
            if (config.MaximumDelay < config.MinimumDelay) config.MaximumDelay = config.MinimumDelay;
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Enabled"] = "Auto doors enabled. Your door delay is {0} seconds.",
                ["Disabled"] = "Auto doors disabled.",
                ["DelaySet"] = "Your door auto close delay is now {0} seconds.",
                ["DelayRange"] = "Delay must be a number between {0} and {1} seconds.",
                ["NoPermission"] = "You do not have permission to use auto doors."
            }, this);
        }

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

        private void Unload()
        {
            foreach (var entry in timers)
            {
                entry.Value?.Destroy();
            }

            timers.Clear();
            SaveData();
        }

        private void LoadData()
        {
            try
            {
                data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(DataName);
            }
            catch
            {
                data = null;
            }

            if (data == null)
            {
                data = new StoredData();
            }

            if (data.Players == null)
            {
                data.Players = new Dictionary<string, PlayerSettings>();
            }
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(DataName, data);

        private PlayerSettings GetSettings(string userId, BasePlayer player = null)
        {
            PlayerSettings settings;
            if (!data.Players.TryGetValue(userId, out settings))
            {
                settings = new PlayerSettings
                {
                    Enabled = config.DefaultEnabled,
                    Delay = Clamp(config.DefaultDelay)
                };
                data.Players[userId] = settings;
            }

            settings.Delay = Clamp(settings.Delay);
            return settings;
        }

        private float Clamp(float value)
        {
            if (value < config.MinimumDelay) return config.MinimumDelay;
            if (value > config.MaximumDelay) return config.MaximumDelay;
            return value;
        }

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

        private void OnDoorOpened(Door door, BasePlayer player)
        {
            if (door == null || player == null || player.IsNpc) return;
            if (player.IsDead() || player.IsSleeping()) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse)) return;

            ulong doorId = door.net.ID.Value;
            Timer oldTimer;
            if (timers.TryGetValue(doorId, out oldTimer))
            {
                oldTimer?.Destroy();
                timers.Remove(doorId);
            }

            PlayerSettings settings = GetSettings(player.UserIDString, player);
            if (!settings.Enabled) return;

            string userId = player.UserIDString;
            float delay = Clamp(settings.Delay);

            timers[doorId] = timer.Once(delay, () => CloseDoor(doorId, door, userId));
        }

        private void CloseDoor(ulong doorId, Door door, string userId)
        {
            timers.Remove(doorId);

            if (door == null || door.IsDestroyed) return;
            if (!permission.UserHasPermission(userId, PermUse)) return;

            PlayerSettings settings;
            if (!data.Players.TryGetValue(userId, out settings) || !settings.Enabled) return;

            door.SetOpen(false);
        }

        [ChatCommand("autodoor")]
        private void CmdAutoDoor(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;

            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }

            PlayerSettings settings = GetSettings(player.UserIDString, player);

            if (args == null || args.Length == 0)
            {
                settings.Enabled = !settings.Enabled;
                SaveData();

                if (settings.Enabled)
                {
                    player.ChatMessage(Msg("Enabled", player.UserIDString, settings.Delay.ToString("0.#")));
                }
                else
                {
                    player.ChatMessage(Msg("Disabled", player.UserIDString));
                }

                return;
            }

            float requested;
            if (!float.TryParse(args[0], out requested))
            {
                player.ChatMessage(Msg("DelayRange", player.UserIDString,
                    config.MinimumDelay.ToString("0.#"), config.MaximumDelay.ToString("0.#")));
                return;
            }

            settings.Delay = Clamp(requested);
            settings.Enabled = true;
            SaveData();

            player.ChatMessage(Msg("DelaySet", player.UserIDString, settings.Delay.ToString("0.#")));
        }
    }
}

Hooks used: OnDoorOpened

Config file

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

AutoDoors.json
{
  "Default enabled": true,
  "Default delay (seconds)": 8,
  "Minimum delay (seconds)": 3,
  "Maximum delay (seconds)": 30,
  "Players": {
    "Enabled": true,
    "Delay (seconds)": 8
  }
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/autodoorTurns automatic door closing on or off for yourself and reports the current delay.autodoors.use
/autodoor <seconds>Sets your personal close delay in seconds, clamped to the configured minimum and maximum.autodoors.use
Permissions
PermissionGrants
autodoors.useAllows using /autodoor and having your doors closed automatically.

How the code works

  1. Catch the door opening

    private void OnDoorOpened(Door door, BasePlayer player) runs when a door opens. The plugin ignores the call when door or player is null, when player.IsNpc is true, when the player is dead or sleeping, or when they lack autodoors.use. It also returns without scheduling a timer when the player's saved Enabled flag is false. The lookup key for their settings is player.UserIDString in the Players dictionary of the data file.

  2. Per-player settings and limits

    Each player gets a PlayerSettings entry in StoredData.Players holding Enabled and Delay. The config keys Minimum delay (seconds) and Maximum delay (seconds) clamp whatever is stored or requested; Default enabled and Default delay (seconds) seed a new entry the first time a player is seen. Clamping happens in the Clamp helper, which is applied when an entry is created and again every time a door opens, so lowering the config limits takes effect immediately.

  3. Schedule the close safely

    The hook starts timer.Once(delay, ...) and stores it in the timers dictionary keyed by door.net.ID.Value. The callback CloseDoor checks that the door is not null or destroyed, re-checks autodoors.use for the player id, and re-reads the saved Enabled flag, so a door will not close for someone who just turned the feature off or lost the permission. Only then does it call door.SetOpen(false).

  4. Avoid double timers

    If the same door is opened twice in quick succession, two timers would fight over it. Before scheduling, the hook looks up door.net.ID.Value in timers, destroys any existing timer and removes the entry. CloseDoor removes the entry again when it runs, and Unload destroys every remaining timer and clears the dictionary, so it does not grow as doors are destroyed or the plugin reloads.

  5. Data storage

    LoadData reads oxide/data/AutoDoors with Interface.Oxide.DataFileSystem.ReadObject<StoredData>(DataName), falling back to a fresh StoredData if the read throws or returns null. SaveData writes it back with WriteObject. The plugin saves in /autodoor right after changing a setting so a crash cannot lose a preference, and again in Unload. GetSettings creates a missing entry on demand.

  6. The /autodoor command

    The chat command is registered with [ChatCommand("autodoor")] and checks autodoors.use first. With no argument it flips Enabled, saves and replies with either the enabled message and the current delay or the disabled message. With an argument it parses a float, runs it through Clamp, sets Delay, forces Enabled to true, saves and confirms. All replies are lang keys Enabled, Disabled, DelaySet, DelayRange and NoPermission.

Install it on your server

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

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

Variations

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

  • Close after a fixed server-wide delay

    Removes per-player settings and uses one delay for everyone.

    Generate this version
  • Ignore doors that were auto-closed once

    Adds a cooldown so the same player cannot have a door slammed repeatedly within a few seconds.

    Generate this version
  • Warn the player before closing

    Sends a chat message one second before the door shuts.

    Generate this version

FAQ

Will this close doors opened by raiders who are not the owner?

Yes. The hook fires for whoever opened the door and has the permission and the feature enabled. It does not check building privilege or door ownership. If you only want doors closed for authorized players, add a door.GetBuildingPrivilege()?.IsAuthed(player) check inside OnDoorOpened.

Does it work on car doors, gates and garage doors?

It targets Door, which covers the standard hinged doors and gate types that derive from it. Vehicle doors are a different component and are not affected. If a particular prefab does not close, test it with a single player before rolling it out.

What happens if the player disconnects while the door is open?

The timer still runs, and CloseDoor closes the door once the delay passes as long as the door exists and the player still has autodoors.use. Player preferences are stored by Steam ID, so a reconnect does not lose the setting.

How do I install it on Oxide or Carbon?

Drop the compiled or .cs file into oxide/plugins. Oxide compiles it on load, Carbon loads the same file. Grant autodoors.use with oxide.grant group default autodoors.use, then edit oxide/config/AutoDoors.json and reload the plugin.

Where are player preferences stored?

In oxide/data/AutoDoors.json, in the Players dictionary keyed by Steam ID, each entry with Enabled and Delay (seconds). Deleting a player's entry restores the default enabled state and delay on their next door open. Delete the file to reset everyone at once.

Customize it for your server

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

Prompt: Create a Rust Oxide plugin that closes doors 3 to 30 seconds after a player opens them, default 8 seconds, with /autodoor toggle and delay, stored per player in oxide/data.
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 closing doors

Customize