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
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.
{
"Default enabled": true,
"Default delay (seconds)": 8,
"Minimum delay (seconds)": 3,
"Maximum delay (seconds)": 30,
"Players": {
"Enabled": true,
"Delay (seconds)": 8
}
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/autodoor | Turns 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 |
| Permission | Grants |
|---|---|
autodoors.use | Allows using /autodoor and having your doors closed automatically. |
How the code works
Catch the door opening
private void OnDoorOpened(Door door, BasePlayer player)runs when a door opens. The plugin ignores the call whendoororplayeris null, whenplayer.IsNpcis true, when the player is dead or sleeping, or when they lackautodoors.use. It also returns without scheduling a timer when the player's savedEnabledflag is false. The lookup key for their settings isplayer.UserIDStringin thePlayersdictionary of the data file.Per-player settings and limits
Each player gets a
PlayerSettingsentry inStoredData.PlayersholdingEnabledandDelay. The config keysMinimum delay (seconds)andMaximum delay (seconds)clamp whatever is stored or requested;Default enabledandDefault delay (seconds)seed a new entry the first time a player is seen. Clamping happens in theClamphelper, which is applied when an entry is created and again every time a door opens, so lowering the config limits takes effect immediately.Schedule the close safely
The hook starts
timer.Once(delay, ...)and stores it in thetimersdictionary keyed bydoor.net.ID.Value. The callbackCloseDoorchecks that the door is not null or destroyed, re-checksautodoors.usefor the player id, and re-reads the savedEnabledflag, so a door will not close for someone who just turned the feature off or lost the permission. Only then does it calldoor.SetOpen(false).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.Valueintimers, destroys any existing timer and removes the entry.CloseDoorremoves the entry again when it runs, andUnloaddestroys every remaining timer and clears the dictionary, so it does not grow as doors are destroyed or the plugin reloads.Data storage
LoadDatareadsoxide/data/AutoDoorswithInterface.Oxide.DataFileSystem.ReadObject<StoredData>(DataName), falling back to a freshStoredDataif the read throws or returns null.SaveDatawrites it back withWriteObject. The plugin saves in/autodoorright after changing a setting so a crash cannot lose a preference, and again inUnload.GetSettingscreates a missing entry on demand.The /autodoor command
The chat command is registered with
[ChatCommand("autodoor")]and checksautodoors.usefirst. With no argument it flipsEnabled, 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 throughClamp, setsDelay, forcesEnabledto true, saves and confirms. All replies are lang keysEnabled,Disabled,DelaySet,DelayRangeandNoPermission.
Install it on your server
Save AutoDoors.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/AutoDoors.json(Carbon:carbon/configs/AutoDoors.json). After editing, reload it:oxide.reload AutoDoors c.reload AutoDoors # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip: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 versionIgnore 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 versionWarn 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.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More building plugins
- Auto code lockAutomatically 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.OnServerInitializedOnEntityBuilt
- 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 closing doors
Customize