Skip to content

plugin sdk configuration

Andre Lafleur edited this page Jul 10, 2026 · 11 revisions

About plugin configuration

Plugins store configuration in the Role.SpecificConfiguration property as a serialized string (typically JSON).

Configuration architecture

PluginDescriptor.SpecificDefaultConfig
    ↓
Provides default configuration when role is created
    ↓
Role.SpecificConfiguration
    ↓
Stored in Security Center database
    ↓
Plugin reads on startup and monitors for changes

Key points:

  • Configuration is stored per-role instance
  • Multiple instances of same plugin have separate configurations
  • Changes persist across plugin restarts
  • Configuration changes trigger the plugin role's FieldsChanged event
  • Configuration is a string, plugin chooses serialization format

Default configuration

Define defaults in PluginDescriptor.SpecificDefaultConfig:

public class MyPluginDescriptor : PluginDescriptor
{
    public override string SpecificDefaultConfig
    {
        get
        {
            var config = new PluginConfig
            {
                ServerUrl = "http://localhost:8080",
                PollInterval = 30,
                EnableLogging = true,
                Timeout = TimeSpan.FromSeconds(10)
            };
            return JsonConvert.SerializeObject(config);
        }
    }
}

When applied:

  • Applied when plugin role is first created in Config Tool
  • Not applied to existing roles (preserves customization)
  • If SpecificDefaultConfig returns empty string, no default applied

Serialization format:

  • JSON is recommended (human-readable, well-supported)
  • XML is also supported
  • SpecificConfiguration is a plain string, so a binary format must be encoded first (for example, Base64)

Reading configuration

Read configuration in OnPluginLoaded():

Inside a Plugin class, Engine is the SDK engine provided by the plugin host, and PluginGuid is the GUID of the current plugin role. Use Engine.GetEntity<Role>(PluginGuid) to read that role's SpecificConfiguration.

public class PluginConfig
{
    public string ServerUrl { get; set; }
    public int PollInterval { get; set; }
    public bool EnableLogging { get; set; }
    public TimeSpan Timeout { get; set; }
    public int SchemaVersion { get; set; } = 2;
}

protected override void OnPluginLoaded()
{
    var config = LoadConfiguration();
    ApplyConfiguration(config);
}

private PluginConfig LoadConfiguration()
{
    var role = Engine.GetEntity<Role>(PluginGuid);
    string configJson = role.SpecificConfiguration;
    
    if (string.IsNullOrEmpty(configJson))
    {
        Logger.TraceWarning("No configuration found, using defaults");
        return CreateDefaultConfiguration();
    }
    
    try
    {
        return JsonConvert.DeserializeObject<PluginConfig>(configJson);
    }
    catch (Exception ex)
    {
        Logger.TraceError(ex, "Failed to parse configuration, using defaults");
        return CreateDefaultConfiguration();
    }
}

private PluginConfig CreateDefaultConfiguration()
{
    return new PluginConfig
    {
        ServerUrl = "http://localhost:8080",
        PollInterval = 30,
        EnableLogging = true,
        Timeout = TimeSpan.FromSeconds(10)
    };
}

private void ApplyConfiguration(PluginConfig config)
{
    Logger.TraceInformation($"Applying configuration: ServerUrl={config.ServerUrl}");
    
    m_serverUrl = config.ServerUrl;
    m_pollInterval = config.PollInterval;
    m_enableLogging = config.EnableLogging;
    m_timeout = config.Timeout;
}

Best practices:

  • Always handle missing/invalid configuration gracefully
  • Log configuration values (except secrets)
  • Validate configuration values
  • Fall back to safe defaults on errors

Updating configuration

Use this pattern when the plugin changes settings stored on its own role. For settings changed by an administrator, assign Role.SpecificConfiguration in the Config Tool configuration page's Save() method.

private void UpdateConfiguration(PluginConfig config)
{
    Engine.TransactionManager.ExecuteTransaction(() =>
    {
        var role = Engine.GetEntity<Role>(PluginGuid);
        role.SpecificConfiguration = JsonConvert.SerializeObject(config);
    });
    
    Logger.TraceInformation("Configuration updated");
}

When to update from plugin code:

  • To save settings that the plugin creates and needs after a restart
  • To adjust default values for this role

Considerations:

  • The value is stored on this role. Other roles created from the same plugin type keep their own SpecificConfiguration.
  • Other SDK code can detect the role update with Role.FieldsChanged, or with Engine.EntitiesInvalidated by checking for this role GUID.
  • Use a transaction to group this value with other entity changes that must be saved together.

Monitoring configuration changes

Subscribe to the plugin role's FieldsChanged event to detect configuration changes:

private Role m_pluginRole;

protected override void OnPluginLoaded()
{
    m_pluginRole = Engine.GetEntity<Role>(PluginGuid);
    m_pluginRole.FieldsChanged += OnPluginRoleFieldsChanged;
    
    // Load initial configuration
    var config = LoadConfiguration();
    ApplyConfiguration(config);
}

private void OnPluginRoleFieldsChanged(object sender, FieldsChangedEventArgs e)
{
    Logger.TraceDebug("Plugin configuration changed");
    
    // Reload and apply configuration
    var config = LoadConfiguration();
    ApplyConfiguration(config);
}

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        if (m_pluginRole != null)
        {
            m_pluginRole.FieldsChanged -= OnPluginRoleFieldsChanged;
        }
    }
}

When raised:

  • Configuration changed in Config Tool
  • Configuration updated programmatically by SDK client
  • Any other role property changed
  • Occurs on the engine thread (safe to use Engine)

Configuration validation

Validate configuration values so a bad edit in Config Tool cannot put the plugin into a broken state:

private bool ValidateConfiguration(PluginConfig config, out string error)
{
    if (string.IsNullOrWhiteSpace(config.ServerUrl))
    {
        error = "ServerUrl is required";
        return false;
    }
    
    if (!Uri.IsWellFormedUriString(config.ServerUrl, UriKind.Absolute))
    {
        error = "ServerUrl is not a valid URL";
        return false;
    }
    
    if (config.PollInterval < 1 || config.PollInterval > 3600)
    {
        error = "PollInterval must be between 1 and 3600 seconds";
        return false;
    }
    
    if (config.Timeout < TimeSpan.FromSeconds(1))
    {
        error = "Timeout must be at least 1 second";
        return false;
    }
    
    error = null;
    return true;
}

Call ValidateConfiguration from LoadConfiguration (shown in Reading configuration above) right after deserializing, and fall back to CreateDefaultConfiguration() when it fails:

if (!ValidateConfiguration(config, out string error))
{
    Logger.TraceError($"Invalid configuration: {error}");
    ModifyPluginState(new PluginStateEntry("Configuration",
        $"Invalid configuration: {error}") { IsWarning = true });
    return CreateDefaultConfiguration();
}

Validation strategies:

  • Validate on load and on change
  • Report errors via ModifyPluginState()
  • Fall back to safe defaults on validation failure
  • Log validation errors
  • Don't crash plugin on invalid config

Secrets and sensitive data

DO NOT store passwords or API keys in SpecificConfiguration - it is stored as plaintext in the database.

For sensitive data, use RestrictedConfiguration instead:

// WRONG - Plaintext password in SpecificConfiguration
public class BadPluginConfig
{
    public string ServerUrl { get; set; }
    public string Password { get; set; }  // DON'T DO THIS!
}

// CORRECT - Use RestrictedConfiguration for credentials
protected override void OnPluginLoaded()
{
    // Load non-sensitive config from SpecificConfiguration
    var config = LoadConfiguration();
    
    // Load credentials from RestrictedConfiguration
    var role = Engine.GetEntity<Role>(PluginGuid);
    if (role.RestrictedConfiguration.TryGetPrivateValue("Password", out SecureString securePassword))
    {
        try
        {
            InitializeConnection(config.ServerUrl, SecureStringToString(securePassword));
        }
        finally
        {
            securePassword.Dispose();
        }
    }
}

See Plugin SDK Restricted Configuration for secure credential storage, including the SecureStringToString helper used above.

Configuration storage comparison

Storage Purpose Format Security Access
SpecificConfiguration General settings Any string (JSON recommended) Plaintext Anyone with role access
RestrictedConfiguration.AdminConfigXml Admin config Any string (no XML-specific handling) Plaintext Plugin or administrators
RestrictedConfiguration (methods) Credentials SecureString Secure Plugin only (read), admin (write)

Best practice: Store non-sensitive configuration in SpecificConfiguration, sensitive credentials in RestrictedConfiguration using TryGetPrivateValue()/SetPrivateValue() methods.

Configuration migration

Handle configuration schema changes between versions. PluginConfig's SchemaVersion property (shown in Reading configuration above, defaulting to the current version) lets the plugin tell an older, deserialized configuration apart from a current one:

private bool NeedsMigration(PluginConfig config)
{
    return config.SchemaVersion < 2;
}

private PluginConfig MigrateConfiguration(PluginConfig config)
{
    if (config.SchemaVersion == 1)
    {
        // Schema version 1 predates the Timeout property; default it.
        if (config.Timeout == TimeSpan.Zero)
        {
            config.Timeout = TimeSpan.FromSeconds(10);
        }

        config.SchemaVersion = 2;
    }

    return config;
}

Call NeedsMigration/MigrateConfiguration from LoadConfiguration, right after deserializing, and persist the result so the migration only runs once:

var config = JsonConvert.DeserializeObject<PluginConfig>(configJson);

if (NeedsMigration(config))
{
    config = MigrateConfiguration(config);

    Engine.TransactionManager.ExecuteTransaction(() =>
    {
        role.SpecificConfiguration = JsonConvert.SerializeObject(config);
    });

    Logger.TraceInformation("Configuration migrated to the current schema version");
}

Migration best practices:

  • Include schema version in configuration
  • Support migrating from any previous version
  • Log migrations

Configuration UI

A custom configuration page in Config Tool reads and writes SpecificConfiguration directly through Workspace.Sdk (the IEngine a ConfigPage runs against), the same way the plugin itself reads and writes it through Engine. SpecificConfiguration is a plain property on the Role entity, which the config page already has access to once it resolves the entity in its Entity setter, so no request/response round trip through the plugin is involved:

protected override void Refresh()
{
    m_configuration.Load(m_role.SpecificConfiguration);
}

protected override void Save()
{
    Workspace.Sdk.TransactionManager.ExecuteTransaction(() =>
    {
        m_role.SpecificConfiguration = m_configuration.Serialize();
    });
}

The plugin picks up the change the same way it picks up any other edit made in Config Tool: through the FieldsChanged event, shown in Monitoring configuration changes above.

For the full ConfigPage implementation, including the Entity setter that resolves m_role and the RoleConfiguration serialization helper, see Configuring plugin roles.

See also

Platform SDK

Plugin SDK

Workspace SDK

Media SDK

Macro SDK

Web SDK

Synergis RIO

Media Gateway

Genetec Web Player

Clone this wiki locally