-
Notifications
You must be signed in to change notification settings - Fork 6
plugin sdk configuration
Plugins store configuration in the Role.SpecificConfiguration property as a serialized string (typically JSON).
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
FieldsChangedevent - Configuration is a string, plugin chooses serialization format
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
SpecificDefaultConfigreturns empty string, no default applied
Serialization format:
- JSON is recommended (human-readable, well-supported)
- XML is also supported
-
SpecificConfigurationis a plainstring, so a binary format must be encoded first (for example, Base64)
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
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 withEngine.EntitiesInvalidatedby checking for this role GUID. - Use a transaction to group this value with other entity changes that must be saved together.
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)
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
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.
| 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.
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
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.
- Plugin SDK overview: Plugin architecture, lifecycle, and components.
- Plugin SDK lifecycle: Plugin initialization, startup, and disposal phases.
- Workspace SDK config pages: Building the Config Tool page that reads and writes this configuration.
- Plugin SDK Restricted Configuration: Storing credentials and other sensitive values.
- About transactions: Batching multiple entity operations into one request.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- SDK compatibility
- Entities
- Entity cache
- Entity loading, cache, and query options
- Transactions
- Events
- Actions
- Security Desk
- Custom events
- ReportManager
- ReportManager query reference
- Access control raw events
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- About custom fields
- About video
- About cameras
- Enrolling a video unit
- Archiver and auxiliary archiver roles
- Archive transfer
- About access control
- About cardholders and credentials
- About doors, areas, elevators, and access points
- About access rules and schedules
- About access control units and interface modules
- Enrolling an access control unit
- Door templates
- Visitors
- Mobile credentials
- About threat levels
- About alarms
- Maps
- Logging
- Overview
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Engine extensions
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- .NET 8 support
- Overview
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Under the hood
- Troubleshooting