Repository navigation
Plugin Settings and Data
Every initialized plugin receives an IPluginDataStore rooted beneath the configured plugin-data directory and isolated by permanent plugin identifier.
await context.DataStore.WriteAsync("schedule-cache", cache, cancellationToken);
ScheduleCache? cache = await context.DataStore.ReadAsync<ScheduleCache>("schedule-cache", cancellationToken);
bool exists = context.DataStore.Exists("schedule-cache");
await context.DataStore.DeleteAsync("schedule-cache", cancellationToken);Use short logical names such as schedule-cache, preferences, or receiver-map. Values are JSON serialized. Use version-tolerant models with nullable new properties and sensible defaults.
The plugin-data directory can change at runtime. The host unloads plugins, recreates contexts against the new directory, and loads existing data found there. Do not permanently cache a physical data-store path.
Implement IPluginSettingsProvider to receive a Settings button in Manage Plugins.
public sealed class Plugin : ISimulcastPlugin, IPluginSettingsProvider
{
public object Settings => Configuration;
public ExampleSettings Configuration { get; } = new();
public IReadOnlyList<PluginSettingOption> GetSettingOptions(string settingKey)
{
return settingKey switch
{
nameof(ExampleSettings.Mode) => new[]
{
new PluginSettingOption("off", "Off"),
new PluginSettingOption("automatic", "Automatic")
},
_ => Array.Empty<PluginSettingOption>()
};
}
public Task OnSettingChangedAsync(string settingKey, CancellationToken cancellationToken = default)
{
_context!.Logger.LogInformation("Setting {SettingKey} changed.", settingKey);
return ApplySettingAsync(settingKey, cancellationToken);
}
}The host loads saved attributed values before enabling the plugin and persists changes through the plugin data store.
[PluginSetting("Display name", "Name shown in the plugin card.", PluginSettingControlType.Text, Group = "General", Order = 1)]
public string DisplayName { get; set; } = "Example";| Attribute field | Meaning |
|---|---|
Name |
User-facing label |
Description |
Help text beneath or beside the editor |
ControlType |
Editor to render |
Group |
Section heading; default is General
|
Order |
Sort order inside the group |
SelectedItemsName |
Right-side heading for SideBySideList; default is Selected
|
Only public readable/writable properties carrying PluginSettingAttribute are displayed.
| Control | Recommended property | Options required | Use |
|---|---|---|---|
Text |
string |
No | Names, URLs, IP text, templates |
Numeric |
Numeric CLR type | No | Intervals, limits, ports |
Toggle |
bool |
No | Prominent on/off behavior |
Checkbox |
bool |
No | Conventional boolean preference |
MultiCheckbox |
Collection of values | Yes | Small set of independent choices |
Dropdown |
string or compatible value |
Yes | Exactly one choice |
SideBySideList |
Collection of values | Yes | Larger include/exclude or ordering workflow |
[PluginSetting("Feed address", "Multicast address used by this plugin.", PluginSettingControlType.Text, Group = "Feed", Order = 1)]
public string FeedAddress { get; set; } = "239.0.0.1";
[PluginSetting("Port", "UDP port from 1 to 65535.", PluginSettingControlType.Numeric, Group = "Feed", Order = 2)]
public int Port { get; set; } = 5000;
[PluginSetting("Auto play", "Start playback when a receiver is selected.", PluginSettingControlType.Toggle, Group = "Feed", Order = 3)]
public bool AutoPlay { get; set; } = true;Validate again inside OnSettingChangedAsync; UI control type alone is not domain validation.
[PluginSetting("Filter mode", "Choose how selected titles are treated.", PluginSettingControlType.Dropdown, Group = "Filtering", Order = 1)]
public string FilterMode { get; set; } = "off";PluginSettingOption.Value is persisted; DisplayName is shown. Always set a default that matches one of the option values.
[PluginSetting("Event titles", "Drag titles to the active list.", PluginSettingControlType.SideBySideList, Group = "Filtering", Order = 2, SelectedItemsName = "Blocked Titles")]
public List<string> BlockedTitles { get; set; } = new();Return every available title from GetSettingOptions. The host renders unused choices on the left and selected values on the right.
Include a schema version in complex stored objects:
public sealed class StoredState
{
public int SchemaVersion { get; set; } = 2;
public Dictionary<Guid, ReceiverPreference> Receivers { get; set; } = new();
}Read old versions, migrate in memory, then write the current model. Log migrations at Information; log recoverable malformed user data at Warning and fall back safely.