Config and data
Every plugin gets its own data folder, and a typed JSON config file inside it. You describe the config as a plain class, and the server writes the defaults on first run, reads your edits, and keeps the last good values if an edit is broken.
The data folder
Section titled “The data folder”plugin.DataFolder (the same as plugin.Config.DataFolder) is the plugin’s folder: plugins/<id>/data/ for a plugin running in the plugin worker, which is how plugins run. It is created the first time you use it. Put every file the plugin writes there.
Hot reload watches the plugin’s assembly, not the data folder, so changing a file in it never reloads the plugin.
Typed config
Section titled “Typed config”Describe the settings as a class with defaults, and register it with System.Text.Json source generation (no reflection at run time, AOT-friendly):
using System.Text.Json.Serialization;
public sealed class HomesConfig{ public int MaxHomes { get; set; } = 3; public int CooldownSeconds { get; set; } = 5; public string Greeting { get; set; } = "Welcome home!";}
[JsonSerializable(typeof(HomesConfig))][JsonSourceGenerationOptions(WriteIndented = true)]internal partial class MyJson : JsonSerializerContext { }Load it in OnEnable:
[PluginInfo("HomesConfig", Version = "1.0.0")]public sealed class HomesConfigPlugin : Plugin{ private Config<HomesConfig> _config = null!;
protected override void OnEnable() { _config = Config.Load(MyJson.Default.HomesConfig); // plugins/<id>/data/config.json int max = _config.Value.MaxHomes; // an immutable snapshot _config.Changed += config => Log.Info($"config reloaded: {config.MaxHomes} homes"); _config.Watch = true; // pick up edits made on disk }}Config.Load returns a Config<T>:
| Member | Behaviour |
|---|---|
Config.Load(typeInfo, fileName) |
reads config.json (or fileName) in the data folder; if it does not exist, the defaults (new T(), including property initialisers) are written first |
Value |
the current values: an immutable snapshot, replaced as a whole on reload; safe to read from any thread |
Reload() |
reads the file again; returns false, and keeps the old values, when the file is invalid |
Save() |
writes the current value, pretty-printed |
Update(value) |
replaces the value and saves it, then raises Changed |
Changed |
an event with the new value, raised on the tick thread after a reload that produced a value |
Watch |
when true, edits on disk are picked up (debounced) and reported through Changed |
Path |
the file’s full path |
Comments and trailing commas are allowed when the file is read, so admins can annotate it. A parse error keeps the previous values (or the defaults on the first load) and logs the file and the line. The broken file is never overwritten after an error.
Your own files
Section titled “Your own files”For data that is not configuration (homes per player, a ban list), plugin.Config offers two helpers for files in the data folder:
ReadText(fileName)returns the text, ornullwhen the file does not exist.WriteText(fileName, text)writes atomically (a temp file, then a rename), so a crash never leaves half a file.
Names are relative to the data folder: a rooted path, a .. or a reserved character throws ArgumentException. Serialise your own types with the same source-generated context:
if (Config.ReadText("homes.json") is { } json) homes = JsonSerializer.Deserialize(json, MyJson.Default.HomesData) ?? new HomesData();Config.WriteText("homes.json", JsonSerializer.Serialize(homes, MyJson.Default.HomesData));Do the disk work off the tick thread for anything big (see threading).
What survives a reload
Section titled “What survives a reload”Your plugin’s fields do not survive a hot reload; the ECS component values of the entities do, when the component’s key and layout are unchanged (see hot reload). State that must outlive a reload belongs in components or on disk.