-
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
EntitiesInvalidatedevent - 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
- Binary formats work but harder to troubleshoot
- Must be string-serializable
Read configuration in OnPluginLoaded():
public class PluginConfig
{
public string ServerUrl { get; set; }
public int PollInterval { get; set; }
public bool EnableLogging { get; set; }
public TimeSpan Timeout { get; set; }
}
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 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
Plugins can programmatically update their configuration:
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:
- In response to request from config page
- After auto-discovery of settings
- To persist runtime state changes
- When defaults need adjustment
Considerations:
- Updates trigger
EntitiesInvalidatedevent - Other plugin instances will see the change
- Changes are immediately visible in Config Tool
- Use transactions to ensure atomicity
Subscribe to EntitiesInvalidated to detect configuration changes:
protected override void OnPluginLoaded()
{
Engine.EntitiesInvalidated += OnEntitiesInvalidated;
// Load initial configuration
var config = LoadConfiguration();
ApplyConfiguration(config);
}
private void OnEntitiesInvalidated(object sender, EntitiesInvalidatedEventArgs e)
{
// Check if our role was updated
var roleUpdate = e.Entities.FirstOrDefault(u => u.EntityGuid == PluginGuid);
if (roleUpdate != null)
{
Logger.TraceDebug("Plugin configuration changed");
// Reload and apply configuration
var config = LoadConfiguration();
ApplyConfiguration(config);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Engine.EntitiesInvalidated -= OnEntitiesInvalidated;
}
}When fired:
- 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:
private PluginConfig LoadConfiguration()
{
var config = ParseConfiguration();
if (!ValidateConfiguration(config, out string error))
{
Logger.TraceError($"Invalid configuration: {error}");
ModifyPluginState(new PluginStateEntry("Configuration",
$"Invalid configuration: {error}") { IsError = true });
return CreateDefaultConfiguration();
}
return config;
}
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;
}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 PluginConfig
{
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 var securePassword))
{
// Use secure password
InitializeConnection(config.ServerUrl, securePassword);
securePassword.Dispose();
}
}See Plugin SDK Restricted Configuration for secure credential storage.
| Storage | Purpose | Format | Security | Access |
|---|---|---|---|---|
SpecificConfiguration |
General settings | JSON/XML/any string | Plaintext | Anyone with role access |
RestrictedConfiguration.AdminConfigXml |
Admin config | XML | 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 GetPrivateValue()/SetPrivateValue() methods.
Handle configuration schema changes between versions:
private PluginConfig LoadConfiguration()
{
var role = Engine.GetEntity<Role>(PluginGuid);
string configJson = role.SpecificConfiguration;
if (string.IsNullOrEmpty(configJson))
return CreateDefaultConfiguration();
try
{
// Try to parse as current version
var config = JsonConvert.DeserializeObject<PluginConfig>(configJson);
// Check if migration needed
if (NeedsMigration(config))
{
config = MigrateConfiguration(config);
// Save migrated configuration
Engine.TransactionManager.ExecuteTransaction(() =>
{
role.SpecificConfiguration = JsonConvert.SerializeObject(config);
});
Logger.TraceInformation("Configuration migrated to new format");
}
return config;
}
catch (Exception ex)
{
Logger.TraceError(ex, "Configuration parse error");
return CreateDefaultConfiguration();
}
}
private bool NeedsMigration(PluginConfig config)
{
// Check for old property or schema version
return config.SchemaVersion < 2;
}
private PluginConfig MigrateConfiguration(PluginConfig config)
{
if (config.SchemaVersion == 1)
{
// Migrate v1 to v2
config.NewProperty = ConvertOldProperty(config.OldProperty);
config.SchemaVersion = 2;
}
return config;
}Migration best practices:
- Include schema version in configuration
- Support migrating from any previous version
- Log migrations
Custom config pages can read/write configuration via RequestManager:
Plugin side:
protected override void OnPluginLoaded()
{
// Use Plugin's protected helper methods to register handlers
AddRequestHandler<GetConfigRequest, PluginConfig>(HandleGetConfig);
AddRequestHandler<SetConfigRequest, SetConfigResponse>(HandleSetConfig);
}
private PluginConfig HandleGetConfig(GetConfigRequest request)
{
return LoadConfiguration();
}
private SetConfigResponse HandleSetConfig(SetConfigRequest request)
{
try
{
if (!ValidateConfiguration(request.Config, out string error))
{
return new SetConfigResponse { Success = false, Error = error };
}
UpdateConfiguration(request.Config);
ApplyConfiguration(request.Config);
return new SetConfigResponse { Success = true };
}
catch (Exception ex)
{
Logger.TraceError(ex, "Failed to update configuration");
return new SetConfigResponse
{
Success = false,
Error = ex.Message
};
}
}For request and response details, see Plugin SDK request manager.
- Plugin SDK overview: Plugin architecture, lifecycle, and components.
- Plugin SDK lifecycle: Plugin initialization, startup, and disposal phases.
- Plugin SDK request manager: Request and response communication between plugin and config pages.
- About transactions: Batching multiple entity operations into one request.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- SDK compatibility
- Entities
- Entity cache
- Transactions
- Events
- Actions
- Security Desk
- Custom events
- ReportManager
- ReportManager query reference
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- Mobile credentials
- Logging
- Overview
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- 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