Skip to content

Plugin Settings and Data

NyxionSoftware edited this page Aug 5, 2026 · 2 revisions

Plugin Settings and Data

Isolated data store

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.

Settings provider

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.

Setting attributes

[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 types

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

Text, numeric, and boolean

[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.

Dropdown

[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.

Side-by-side list

[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.

Data migrations

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.

Clone this wiki locally