Understanding Historical Field Updates in Microsoft Dataverse: CreatedOn, CreatedBy, ModifiedOn, and ModifiedBy

 When working on data migration projects in Dynamics CRM / Dataverse, one of the most common requirements is preserving historical data such as:

  • Created On
  • Created By
  • Modified On
  • Modified By

At first glance, it may seem straightforward to set these fields using the CRM SDK. However, many developers quickly discover that some values are ignored while others work only in specific scenarios.

This blog explains the behavior of these system fields across different execution contexts and clarifies when Dataverse allows overriding them.


Why This Matters

In most enterprise migration projects, business users expect migrated records to retain original audit history from legacy systems.

For example:

FieldExpected Historical Value
Created OnOriginal record creation date
Created ByOriginal creator
Modified OnLast modification date from source system
Modified ByLast modifier from source system

Many developers attempt to achieve this directly through SDK operations, only to find inconsistent behavior.

The key lies in understanding the execution context.


Scenario 1: Using SDK Create from an External Application

Consider the following console application code:

var createEnt = new Entity("account");

createEnt["name"] = "Data Migration Create from Console";

createEnt["overriddencreatedon"] = new DateTime(2018, 03, 01);

createEnt["createdon"] = new DateTime(2017, 03, 01);

createEnt["modifiedon"] = new DateTime(2018, 03, 01);

createEnt["modifiedby"] =
new EntityReference("systemuser", userId);

createEnt["createdby"] =
new EntityReference("systemuser", userId);

service.Create(createEnt);

What Happens?

After execution:

FieldResult
overriddencreatedonApplied
createdonIgnored
createdbyIgnored
modifiedonIgnored
modifiedbyIgnored

Important Observation

When using service.Create() externally:

overriddencreatedon

is the only supported way to preserve historical Created On values.

Even if you explicitly set:

createEnt["createdon"]

Dataverse ignores it.

Instead, the platform internally maps the visible Created On value from:

overriddencreatedon

Key Takeaway

External SDK Create Supports Only:

overriddencreatedon

for historical Created On migration.

The remaining audit fields are controlled by the platform runtime.


Scenario 2: Setting Values Inside a Pre-Create Plugin

Now consider the same logic inside a Pre-Create plugin:

public void Execute(IServiceProvider serviceProvider)
{
var context =
(IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));

var target =
(Entity)context.InputParameters["Target"];

target["createdon"] = new DateTime(2017, 03, 01);

target["modifiedon"] = new DateTime(2018, 03, 01);

target["modifiedby"] =
new EntityReference("systemuser", userId);

target["createdby"] =
new EntityReference("systemuser", userId);
}

Result

This time all values are successfully applied.

FieldWorks?
createdonYes
createdbyYes
modifiedonYes
modifiedbyYes
overriddencreatedonIgnored

Why Does Plugin Execution Work?

The answer lies in the Dataverse execution pipeline.

A Pre-Create plugin runs before Dataverse finalizes system values and commits the record to the database.

At this stage:

  • system fields are still mutable
  • plugins can override audit fields
  • the platform respects those values

This is fundamentally different from external SDK execution.


Most Important Insight

External SDK Context

Dataverse controls:

  • CreatedBy
  • ModifiedBy
  • ModifiedOn

and ignores custom values.


Pre-Create Plugin Context

Developers can override:

  • CreatedOn
  • CreatedBy
  • ModifiedOn
  • ModifiedBy

before database persistence.


Scenario 3: Pre-Update Plugin

A Pre-Update plugin behaves similarly.

If you set:

target["modifiedon"]
target["modifiedby"]

inside Pre-Update, Dataverse respects the values.

Although this scenario is less common for migration projects, it follows the same pipeline behavior.


Scenario 4: SDK Update Method

Now consider:

service.Update(entity);

Attempting to update historical fields using SDK update operations does not work.

Even:

entity["overriddencreatedon"]

has no effect during update operations.


Summary Table

Scenariocreatedonoverriddencreatedonmodifiedoncreatedbymodifiedby
SDK Create
Plugin Pre-Create
Plugin Pre-Update
SDK Update

Recommended Approach for Data Migration

For enterprise-grade migrations, the most reliable pattern is:

Migration Tool

Dataverse SDK Create

Pre-Create Plugin

Set Historical Audit Fields

This architecture ensures:

  • audit fields are preserved
  • migration remains supported
  • business expectations are met

Final Thoughts

Historical data preservation in Dataverse is highly dependent on execution context.

The most common misconception is assuming that SDK operations can directly update all audit fields. In reality:

  • overriddencreatedon works only during SDK Create
  • audit fields like createdby and modifiedby can only be overridden within plugin pipeline stages

Understanding this distinction can save significant debugging effort during migration projects.

For anyone working on CRM/Dataverse data migration, this behavior is essential knowledge before designing migration architecture.

Comments