XrmGhost.Attributes is a .NET library of custom attributes for declarative Dataverse plugin development. Decorate your plugin classes with these attributes to declare execution context, message handling, entity images, input/output parameters, and configuration — without writing plumbing code.
The package targets netstandard2.0 and has no runtime dependencies beyond the BCL, so it can be referenced by any Dataverse plugin project.
- Documentation: https://docs.xrmghost.tech
- Releases: GitHub Releases
You can reference XrmGhost.Attributes in any Dataverse plugin project — no other XrmGhost component is required.
The attributes are pure .NET attribute classes. Adding them to your plugin decorates the assembly with structured metadata that can be read at runtime or build time via reflection. On their own they impose zero runtime overhead inside the Dataverse sandbox: a [PreImage] or [InputParameter] attribute on a class does nothing unless something reads it.
Typical standalone uses:
- Custom tooling. Write a registration or scaffolding tool that reads attributes to automate plugin step registration.
- Documentation generation. Produce an audit of all messages, entities, and images a plugin assembly handles, without parsing code.
- Test harnesses. Read
[InputParameter]and[PreImage]attributes to reconstruct a plausibleIPluginExecutionContextin unit tests.
Within the XrmGhost ecosystem this package is consumed by two components:
xrmghost-framework-host is a thin hosting layer that wraps plugin execution. At runtime it reads the attributes on a plugin class via reflection and automatically populates the corresponding properties before calling Execute. This removes the standard boilerplate of extracting Target, pre/post images, and configuration strings from IPluginExecutionContext.
The generate-scenario command of the XrmGhost CLI introspects a plugin assembly and generates test scenario files. It uses the attribute values (entity JSON, parameter JSON, image aliases) as the starting data for each scenario.
Note:
generate-scenarioexpects plugins to be decorated with XrmGhost.Attributes attributes. If the attributes are absent the CLI degrades gracefully with a warning, but no scenario data will be pre-populated.
Install the package from NuGet:
dotnet add package XrmGhost.AttributesOr add the reference directly to your .csproj:
<PackageReference Include="XrmGhost.Attributes" />All attributes target AttributeTargets.Class — they must be placed on the plugin class, not on individual members.
| Attribute | Description |
|---|---|
PluginExecutionConfigAttribute |
Declares entity + messages the plugin handles; supports stage, mode, and impersonation. Repeatable. |
HandlesMessageAttribute |
Declares a single SDK message the plugin handles. Repeatable. |
InputParameterAttribute |
Declares an input parameter name and its default JSON value. Repeatable. |
OutputParameterAttribute |
Declares an expected output parameter name and its expected JSON value. Repeatable. |
PreImageAttribute |
Declares a pre-operation entity image alias and its JSON seed value. Repeatable. |
PostImageAttribute |
Declares a post-operation entity image alias and its JSON seed value. Repeatable. |
SecureConfigurationAttribute |
Declares the default secure configuration string. Single use. |
UnsecureConfigurationAttribute |
Declares the default unsecure configuration string. Single use. |
SharedVariableAttribute |
Declares a shared variable name and its default JSON value. Repeatable. |
SolutionComponentAttribute |
Declares which solution(s) the plugin should be registered in. Repeatable. |
All attributes live in the XrmGhost.Attributes namespace.
using XrmGhost.Attributes;
using Microsoft.Xrm.Sdk;
[PluginExecutionConfig("account", "Create", "Update")]
[PreImage("primary", "{ \"__entityName\": \"account\", \"accountid\": \"00000000-0000-0000-0000-000000000001\", \"name\": \"Old Name\" }")]
[InputParameter("Target", "{ \"__entityName\": \"account\", \"accountid\": \"00000000-0000-0000-0000-000000000001\" }")]
public class AccountPlugin : IPlugin
{
public Entity TargetEntity { get; set; }
public Entity PreImageEntity { get; set; }
public void Execute(IServiceProvider serviceProvider)
{
// When used with xrmghost-framework-host, TargetEntity and PreImageEntity
// are populated automatically before Execute is called.
}
}using XrmGhost.Attributes;
using Microsoft.Xrm.Sdk;
[PluginExecutionConfig("account", "Create", "Update")]
[PluginExecutionConfig("contact", "Create")]
[PluginExecutionConfig("opportunity", "Update", "Delete")]
[InputParameter("Target", "{ \"__entityName\": \"account\", \"accountid\": \"00000000-0000-0000-0000-000000000001\" }")]
public class MultiEntityPlugin : IPlugin
{
public Entity TargetEntity { get; set; }
public void Execute(IServiceProvider serviceProvider)
{
// Registered for:
// Account — Create, Update
// Contact — Create
// Opportunity — Update, Delete
}
}Filtering images to specific columns improves performance by reducing the payload transmitted to the plugin.
using XrmGhost.Attributes;
using Microsoft.Xrm.Sdk;
[PluginExecutionConfig("account", "Update")]
[InputParameter("Target", "{ \"__entityName\": \"account\", \"accountid\": \"00000000-0000-0000-0000-000000000001\" }")]
[PreImage("primary", "{ \"__entityName\": \"account\", \"name\": \"Old Name\", \"revenue\": 1000.00 }",
Attributes = new[] { "name", "accountid", "createdon", "revenue" })]
[PostImage("updated", "{ \"__entityName\": \"account\", \"name\": \"New Name\", \"revenue\": 2000.00 }",
Attributes = new[] { "name", "modifiedon", "revenue" })]
public class AccountUpdatePlugin : IPlugin
{
public Entity TargetEntity { get; set; }
public Entity PreImageEntity { get; set; }
public Entity PostImageEntity { get; set; }
public void Execute(IServiceProvider serviceProvider) { }
}using XrmGhost.Attributes;
using Microsoft.Xrm.Sdk;
[PluginExecutionConfig("lead", "Create", "Update",
Stage = 20, Mode = 0,
ImpersonatingUserId = "12345678-1234-1234-1234-123456789012")]
[SolutionComponent("LeadManagement")]
[InputParameter("Target", "{ \"__entityName\": \"lead\", \"leadid\": \"00000000-0000-0000-0000-000000000002\" }")]
public class LeadProcessorPlugin : IPlugin
{
public Entity TargetEntity { get; set; }
public void Execute(IServiceProvider serviceProvider) { }
}Stage values: 10 PreValidation · 20 PreOperation · 40 PostOperation.
Mode values: 0 Synchronous · 1 Asynchronous.
using XrmGhost.Attributes;
using Microsoft.Xrm.Sdk;
[SolutionComponent("CoreCRM", IsDefault = true)]
[SolutionComponent("SalesEnhancements")]
[PluginExecutionConfig("opportunity", "Create", "Update", "Delete")]
[InputParameter("Target", "{ \"__entityName\": \"opportunity\", \"opportunityid\": \"00000000-0000-0000-0000-000000000003\" }")]
public class OpportunityPlugin : IPlugin
{
public Entity TargetEntity { get; set; }
public void Execute(IServiceProvider serviceProvider) { }
}using XrmGhost.Attributes;
using Microsoft.Xrm.Sdk;
[PluginExecutionConfig("none", "CalculatePrice")]
[InputParameter("Quantity", "5")]
[InputParameter("UnitPrice", "19.99")]
[OutputParameter("TotalPrice", "99.95")]
[SharedVariable("PricingRuleVersion", "\"v2\"")]
public class CalculatePricePlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider) { }
}using XrmGhost.Attributes;
using Microsoft.Xrm.Sdk;
[PluginExecutionConfig("account", "Create")]
[SecureConfiguration("{ \"apiKey\": \"secret-value\" }")]
[UnsecureConfiguration("{ \"endpoint\": \"https://example.com/api\" }")]
public class ConfiguredPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider) { }
}Contributions are welcome. Please refer to CONTRIBUTING.md for guidelines.