5.3 Pre/Post Images & Organization Service Operations in Plug-ins

Key Takeaways

  • Target in InputParameters only contains attributes the caller actually sent; Pre-Images and Post-Images supply the rest of a record's data before and after the core operation.
  • A Pre-Image captures a record's values before the operation is applied; a Post-Image captures them after -- Pre-Images aren't meaningful on Create and Post-Images aren't meaningful on Delete.
  • Pre-validation steps cannot use entity images at all, since they run before the database transaction opens.
  • IOrganizationService, obtained from IOrganizationServiceFactory, is the primary interface for a plug-in to Create, Update, Delete, Retrieve, RetrieveMultiple, or Execute any other SDK message against Dataverse.
  • The userId passed to CreateOrganizationService controls whether the service runs as the current user, the initiating user, or with full system privileges.
Last updated: July 2026

Not every plug-in that needs data has to query the database for it. Dataverse can hand a plug-in two ready-made snapshots of the record it's operating on -- a Pre-Image and a Post-Image -- and for anything the images don't cover, a plug-in reaches back into Dataverse itself through the Organization service. Both mechanisms exist to solve the same core limitation: InputParameters["Target"] only contains the attributes the caller actually sent, which is frequently not the whole record.

Why Target Alone Isn't Enough

If a user changes only a phone number on an Update, Target contains just telephone1 -- none of the record's other fields are present, because the caller never sent them. A plug-in that needs to compare the new value of a field against its old value, or that needs a field like revenue to run a calculation regardless of what the caller actually edited, cannot get either value from Target alone. That's what entity images solve.

Pre-Images and Post-Images

An entity image is a snapshot of a record's attributes that Dataverse captures and hands to the plug-in alongside the execution context. Images are configured per registered step (in the Plug-in Registration Tool, 5.4), each given a friendly alias -- the string key used to retrieve it in code -- and optionally restricted to a specific set of columns to avoid pulling the whole record.

Pre-ImagePost-Image
Snapshot takenBefore the core operation applies changesAfter the core operation applies changes
ReflectsThe record as it existed in the database prior to this requestThe record as it will be saved, including defaulted and calculated fields
Valid onUpdate, Delete (and other messages with an existing record)Create, Update
Not meaningful onCreate (no "before" record exists)Delete (no "after" record exists)
Available toPre-operation and post-operation stepsPre-operation and post-operation steps

Note that pre-validation steps cannot use images at all -- pre-validation runs before the database transaction opens, ahead of the point where Dataverse can reliably construct either snapshot.

In code, images arrive as dictionaries on the context, keyed by the alias registered for that step:

Entity preImage = context.PreEntityImages["PreImage"];
Entity postImage = context.PostEntityImages["PostImage"];

decimal oldRevenue = preImage.GetAttributeValue<Money>("revenue")?.Value ?? 0m;

A common exam scenario: "a post-operation plug-in on Update needs to compare a field's old value to its new value." The answer is a Pre-Image registered on that step -- the new value is available directly from Target or the Post-Image, but the old value only exists in the Pre-Image, because Target won't contain it unless the caller happened to resend it.

The Organization Service

For anything beyond what images provide -- querying unrelated tables, writing to other records, calling other messages -- a plug-in uses IOrganizationService, obtained through IOrganizationServiceFactory:

IOrganizationService service = factory.CreateOrganizationService(context.UserId);

The userId argument controls whose security context the service runs under: pass context.UserId to run as the user Dataverse is currently processing the request as, respecting that user's security roles and business unit; pass context.InitiatingUserId to run as whoever originally triggered the request chain; or pass null to run with full system privileges, the equivalent of the SYSTEM account, used sparingly since it bypasses the security-role checks the platform would otherwise enforce.

IOrganizationService exposes the same core operations available anywhere in the SDK:

  • Create(Entity) -- returns the new record's Guid
  • Update(Entity) -- saves only the attributes present on the Entity object
  • Delete(string logicalName, Guid id)
  • Retrieve(string logicalName, Guid id, ColumnSet columns) -- always pass an explicit ColumnSet, never ColumnSet(true), so the call doesn't pull every column on the table
  • RetrieveMultiple(QueryExpression or FetchXML query) -- for querying by criteria rather than by ID
  • Execute(OrganizationRequest request) -- the general-purpose entry point for everything that isn't basic CRUD: WhoAmIRequest, AssignRequest, SetStateRequest, calls to custom APIs (5.5), and any other SDK message

Because a plug-in already runs inside an active transaction, calls made through IOrganizationService from within it participate in that same transaction for synchronous steps -- a later failure and rollback undoes writes the plug-in itself made during the same execution, not just the original triggering operation. That transactional coupling, combined with the Depth counter from 5.2, is exactly why a plug-in that calls Update on the same table it's handling Update for needs to guard against re-entering its own pipeline.

Test Your Knowledge

A post-operation plug-in on the Update message needs to know both the old and new value of a field the caller changed. Target contains only the new value the caller submitted. Which image, if registered on the step, supplies the old value?

A
B
C
D
Test Your Knowledge
Matching

Match each scenario to the entity image that correctly supplies the needed data:

Match each item on the left with the correct item on the right

1
Reading the auto-generated primary key GUID for a record right after a Create
2
Reading a field's value as it existed immediately before a Delete removes the record
3
Reading calculated or default values Dataverse applied during an Update's core operation
4
Determining the record's value before this Update changed it