Skip to content
Abhishek Chandrashekar

Dataverse Plugin Cheatsheet

A field-by-field map of a Dataverse plug-in: execution context, images, tracing, and the organization service calls you use most.

Dynamics365,Dataverse,PowerPlatform6min read

A Dataverse plug-in starts at Execute. Cast the services once, then read the context and call the organization service from there.

public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
var tracing = (ITracingService)serviceProvider
.GetService(typeof(ITracingService));
var factory = (IOrganizationServiceFactory)serviceProvider
.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
}

Everything about the current request is on context.

FieldCodeWhat it tells you
MessageNamecontext.MessageNameSDK message being processed (Create, Update, Delete, Assign, and so on)
Stagecontext.StagePipeline position: 10 PreValidation (outside the transaction), 20 PreOperation, 40 PostOperation (30 is platform-only)
Modecontext.Mode0 synchronous (caller waits, shares the transaction) or 1 asynchronous (queued system job)
Depthcontext.DepthTimes the pipeline entered this request. Greater than 1 means it re-entered; use it to stop a loop
CorrelationIdcontext.CorrelationIdShared GUID for every plugin and system job in one user operation
OperationIdcontext.OperationIdGUID of the related system job (populated for asynchronous execution)
OperationCreatedOncontext.OperationCreatedOnWhen that related system job was created
IsExecutingOfflinecontext.IsExecutingOfflineTrue when the plugin runs in Dynamics 365 for Outlook while the client is offline
IsOfflinePlaybackcontext.IsOfflinePlaybackTrue when Outlook is replaying an offline event during sync back to the server
IsInTransactioncontext.IsInTransactionTrue inside the database transaction (PreOperation and synchronous PostOperation). False for PreValidation and async

FieldCodeWhat it tells you
UserIdcontext.UserIdUser the plugin is running as. This user’s security roles are enforced
InitiatingUserIdcontext.InitiatingUserIdUser who actually started the operation, even if a later step impersonates someone else
BusinessUnitIdcontext.BusinessUnitIdBusiness unit of the user in UserId
OwningExtensioncontext.OwningExtensionEntityReference of the registered SDK message processing step that is executing

FieldCodeWhat it tells you
PrimaryEntityNamecontext.PrimaryEntityNameLogical name of the table the message targets (account, contact, and so on)
PrimaryEntityIdcontext.PrimaryEntityIdGUID of that record. Often empty on Create before PostOperation; read OutputParameters["id"] there
SecondaryEntityNamecontext.SecondaryEntityNameSecond table for Associate and Disassociate. Empty for most messages
OrganizationNamecontext.OrganizationNameUnique name of the Dataverse environment this plugin is running in

Read the message payload from context.InputParameters.

KeyCodeWhat it tells you
Targetcontext.InputParameters["Target"]Entity for Create and Update, or EntityReference for Delete
EntityMonikercontext.InputParameters["EntityMoniker"]Record pointer on SetState, when the message does not pass a full Entity
Assigneecontext.InputParameters["Assignee"]User or team EntityReference on Assign
Relationshipcontext.InputParameters["Relationship"]Relationship schema name on Associate and Disassociate
State / Statuscontext.InputParameters["State"]State and status codes on SetState. Status is context.InputParameters["Status"]
Other keyscontext.InputParameters["Query"]Keys that exist only for that message, such as ColumnSet and RelatedEntities

Read results from context.OutputParameters.

KeyCodeWhat it tells you
idcontext.OutputParameters["id"]GUID of the new record on Create. Available in PostOperation
BusinessEntitycontext.OutputParameters["BusinessEntity"]Record returned by Retrieve
Custom resultscontext.OutputParameters["YourResponseProperty"]Other outputs for that message, including custom API response properties

An in-memory bag for this request only. Parent and child contexts can read it. It is not stored on the row.

UseCodeWhat it tells you
Custom flagcontext.SharedVariables["skipPricing"] = trueA later step in the same request can branch without querying again
Calculated valuecontext.SharedVariables["total"] = amountA result computed early in the pipeline and reused by a later stage
Any keycontext.SharedVariables["key"]Read a value a previous step stored

The dictionary key is the image name you registered on the step.

ImageCodeWhat it tells you
Before the changecontext.PreEntityImages["PreImage"]Registered columns as they were before the change. Used on Update and Delete, not Create. Only the columns configured on the step are included

ImageCodeWhat it tells you
After the changecontext.PostEntityImages["PostImage"]Record after the change, with Target merged into the database values. Only the columns configured on the step are included

FieldCodeWhat it tells you
Calling pipelinecontext.ParentContextExecution context of the plugin that triggered this one. Null on the top-level user request. Walk context.ParentContext.MessageName to see the chain

tracing.Trace appends one line to the plug-in trace log. The line shows up when plug-in trace logging is Exception or All. The trace is kept when the transaction rolls back.

What to logCode
A linetracing.Trace("message")
Entrytracing.Trace($"{context.MessageName} {context.Stage}")
An id or optiontracing.Trace($"id={context.PrimaryEntityId}")
Which branch rantracing.Trace("branch: skip, depth > 1")
A slow calltracing.Trace($"retrieve {sw.ElapsedMilliseconds} ms")
Before you throwtracing.Trace(ex.ToString())

Leave secrets and full records out of the log.

CreateOrganizationService returns a service bound to a user. Pass a GUID to choose who the calls run as.

Run asCodeWhat it tells you
Step userfactory.CreateOrganizationService(context.UserId)The user the step is already executing as. That user’s privileges apply
Original callerfactory.CreateOrganizationService(context.InitiatingUserId)Ignores later impersonation
SYSTEMfactory.CreateOrganizationService(null)Bypasses user privilege checks. Use this rarely
Another userfactory.CreateOrganizationService(otherUserId)Impersonates that user. Requires the act-on-behalf-of-another-user privilege

These are the calls on the service returned above.

CallCodeWhat it does
Createservice.Create(entity)Insert a record and return its GUID
Retrieveservice.Retrieve(logicalName, id, columnSet)Read one record by id and a ColumnSet
RetrieveMultipleservice.RetrieveMultiple(query)Query with QueryExpression or FetchXML and return an EntityCollection
Updateservice.Update(entity)Write only the attributes set on the Entity
Deleteservice.Delete(logicalName, id)Remove a record by logical name and id
Associateservice.Associate(logicalName, id, relationship, relatedEntities)Link records through a relationship schema name (1:N or N:N)
Disassociateservice.Disassociate(logicalName, id, relationship, relatedEntities)Remove that link
Executeservice.Execute(request)Send any OrganizationRequest: typed request classes, messages with no dedicated method, and custom APIs

RequestCodeWhat it does
AssignRequestservice.Execute(new AssignRequest())Change the owner to a user or team
SetStateRequestservice.Execute(new SetStateRequest())Set statecode and statuscode (activate, deactivate, resolve)
WinOpportunityRequestservice.Execute(new WinOpportunityRequest())Close an opportunity as won
CloseIncidentRequestservice.Execute(new CloseIncidentRequest())Resolve a case
AddMembersTeamRequestservice.Execute(new AddMembersTeamRequest())Add users to an owner team
GrantAccessRequestservice.Execute(new GrantAccessRequest())Share a record by granting privileges to a user or team
Custom APIservice.Execute(new OrganizationRequest("your_customapi"))Call a custom API or custom action by its unique name