-
Notifications
You must be signed in to change notification settings - Fork 1
Advanced Settings Page
IPluginSettingsPage lets a plugin declare its user-editable settings
declaratively — the host renders the editor, persists the values into the
plugin's IPluginSettings, and tells the plugin when something changed. No
UI code in the plugin.
Implement the interface on the same class as your LoupixPlugin subclass.
public interface IPluginSettingsPage
{
IReadOnlyList<PluginSettingDescriptor> SettingsSchema { get; }
IReadOnlyList<PluginSettingAction> SettingsActions { get; }
void OnSettingsSaved();
}| Member | Notes |
|---|---|
SettingsSchema |
Editable settings in display order. |
SettingsActions |
Optional buttons (e.g. "Test connection"). |
OnSettingsSaved() |
Called after the user's edits have been written to IPluginSettings and persisted to disk. Reconnect, restart polling, re-validate here — values you cached in fields during Initialize are now stale. |
public sealed class PluginSettingDescriptor
{
public required string Key { get; init; }
public required string Label { get; init; }
public PluginSettingKind Kind { get; init; } = PluginSettingKind.Text;
public string Description { get; init; } = string.Empty;
public object DefaultValue { get; init; }
}One editable field.
-
Key— storage key inIPluginSettings. Read the value back withhost.Settings.Get<T>(key)using the type implied byKind(see below). -
Label— field label rendered next to the editor. -
Kind— editor kind / stored type. See PluginSettingKind. -
Description— optional helper text under the field. -
DefaultValue— value used when the key is absent. Match the CLR type the kind stores.
public enum PluginSettingKind
{
Text, // string
Password, // string (masked editor)
Number, // JSON integer — read with Get<long>
Toggle, // bool
Heading // display-only section header, no stored value
}| Kind | Editor | Stored as | Read with |
|---|---|---|---|
Text |
Single-line text box | string |
Get<string>(key) |
Password |
Masked text box | string |
Get<string>(key) |
Number |
Numeric editor (integer) | JSON integer | Get<long>(key) |
Toggle |
On/off switch | bool |
Get<bool>(key) |
Heading |
Bold section label (display only) | – | – |
Heading is useful when you build a dynamic schema and want to group fields
visually. The descriptor's Label is rendered as the heading, the optional
Description as a subtitle. Key is ignored for storage but must be unique
within the schema; DefaultValue is ignored.
public sealed class PluginSettingAction
{
public required string Label { get; init; }
public required Func<Task<string>> Invoke { get; init; }
}A button shown on the settings form. Invoke runs the action and returns a
short status message the host displays inline (e.g. "Connected",
"Failed: timeout"). Keep the string short — the host has limited space.
public sealed class MyPlugin : LoupixPlugin, IPluginSettingsPage
{
private IPluginHost _host = null!;
private MyClient? _client;
public override PluginMetadata Metadata { get; } = new()
{
Id = "myservice", Name = "My Service",
Version = new Version(1, 0, 0), SdkVersion = SdkInfo.Version
};
public override void Initialize(IPluginHost host)
{
_host = host;
RebuildClient();
}
public override IEnumerable<IPluginCommand> GetCommands() => [];
// ---- IPluginSettingsPage ----
public IReadOnlyList<PluginSettingDescriptor> SettingsSchema { get; } =
[
new PluginSettingDescriptor
{
Key = "endpoint", Label = "Endpoint URL",
Kind = PluginSettingKind.Text,
DefaultValue = "https://api.example.com"
},
new PluginSettingDescriptor
{
Key = "token", Label = "API token",
Kind = PluginSettingKind.Password,
DefaultValue = string.Empty,
Description = "Generated in Account → Tokens."
},
new PluginSettingDescriptor
{
Key = "pollSec", Label = "Poll interval (s)",
Kind = PluginSettingKind.Number,
DefaultValue = 5L
},
new PluginSettingDescriptor
{
Key = "verbose", Label = "Verbose logging",
Kind = PluginSettingKind.Toggle,
DefaultValue = false
}
];
public IReadOnlyList<PluginSettingAction> SettingsActions => [
new PluginSettingAction
{
Label = "Test connection",
Invoke = async () =>
{
try { return await _client!.PingAsync() ? "Connected" : "No response"; }
catch (Exception ex) { return $"Failed: {ex.Message}"; }
}
}
];
public void OnSettingsSaved() => RebuildClient();
private void RebuildClient()
{
var endpoint = _host.Settings.Get<string>("endpoint")!;
var token = _host.Settings.Get<string>("token") ?? string.Empty;
_client?.Dispose();
_client = new MyClient(endpoint, token);
}
}Getting started
API reference
Advanced
Operations
Release notes