How to find every writer of a Dataverse field, by hand
No tooling required beyond a solution export and a text editor. This is the full method, worked against a real export, along with the two places it stops working.
The platform will not give you this list. Dependency tracking covers the surfaces that declare their reference in metadata, and misses the ones that keep it inside compiled code, a script file, or a JSON document, which happens to be most of the things that write values. We wrote about why that gap exists separately.
You can still get the answer. It takes an export, a text editor, and a couple of hours per column on a mature environment. Everything below is worked against two real managed-solution exports, deliberately built messy for testing, so the fragments are the actual shapes rather than cleaned-up illustrations.
We will chase one column the whole way through: account.creditlimit.
Start with the export
Export the unmanaged solution containing the components you care about, or a solution you create for the purpose with everything added to it. Rename the .zip and unpack it. You get something like this:
Everything you can find by hand is in there. So is the first surprise.
What the solution declares about your column
Open customizations.xml and look for the entity definitions. In both of the exports used here, the element is present and empty:
Between them those two solutions contain five components that write account.creditlimit. The solution metadata records none of them at column level. The canvas app comes closest, and what it records is the table:
The modern flow does not manage even that. Its entry carries <PrimaryEntity>none</PrimaryEntity> while its definition updates the account table.
Five writers of one column, and the solution's own metadata names the column zero times.
Which is why the rest of this is a reading exercise. The references are all there. They are inside the component definitions, in four different formats, and you have to go get them.
Classic workflows, dialogs and actions
These are the .xaml files. All three are Windows Workflow Foundation documents and, usefully, all three write columns through the same activity, so one search covers the set. Look for SetEntityProperty:
The column is in Attribute, the table is in EntityName, and the UpdateEntity activity below is what commits the change. Grepping every .xaml in the folder for Attribute="creditlimit" is a genuinely reliable way to find classic-workflow writers.
Two things that will slow you down
The value is not next to the attribute. Value="[UpdateStep1_1]" is a variable, and the literal lives in an EvaluateExpression block further up the sequence:
So finding that the column is written takes one search. Finding what gets written means tracing the variable back through the sequence, and a workflow with a dozen steps has a dozen of these.
The file also will not tell you which workflow it is. The class name is the GUID with the hyphens stripped:
Human-readable names live back in customizations.xml, keyed by the same GUID, along with the Category that tells you what kind of thing you are reading. Category 0 is a classic workflow, 1 a dialog, 2 a business rule, 3 an action. In the test export, three different categories all wrote creditlimit through identical markup.
Modern cloud flows
Modern flows also sit in Workflows/, as .json rather than .xaml. The column reference shows up as a parameter key on a Dataverse connector action:
item/creditlimit is your search term, and the pattern generalises: item/<logicalname> in the parameters object of an OpenApiConnection action is a write.
The action says entityName: "accounts", plural, because connector actions take the entity set name. The trigger three lines above says entityname: "account", singular, because it takes the logical name. Search for one spelling and you will silently miss half the references in the same file.
Two other things worth noticing here. The trigger's message and scope are numeric codes, so no search for the word “update” will find them. And this trigger has no filteringattributes parameter at all, which means it fires on every column change on the table rather than on the ones the flow cares about.
Canvas apps
The .msapp in CanvasApps/ is itself a zip. Unpack it and the readable source is under Src/ as YAML, one file per screen:
Form controls bind through DataField, so that is the search. Writes that do not go through a form control show up as PowerFx instead, in which case look for Patch, Update, UpdateIf, Collect, Remove, and SubmitForm. In the test app the submit path is a plain SubmitForm(Form1), which commits every bound card at once, so the binding is the only place the column name appears.
Remember what the solution recorded for this app: the table, and no column. A canvas app that writes one column and one that writes twenty are indistinguishable from outside the .msapp.
Plugin steps
Registrations are straightforward. Open the Plugin Registration Tool, or query the sdkmessageprocessingstep table, and list every step bound to your table. That gives you which plugins run on which message, at which stage, with which filtering attributes.
Filtering attributes are worth reading closely, because a step registered with creditlimit in its filter is reacting to the column, which is not the same as writing it. Both matter for impact analysis, and they answer different questions.
Which columns the plugin actually writes is a different problem. That lives in the compiled assembly. If you have the source, search for the usual shapes:
entity["creditlimit"] = valueandSetAttributeValue("creditlimit", value)for late-bound codeaccount.CreditLimit = valuefor early-bound code, which means the column name only appears in the generated typed-entity class rather than in the pluginIOrganizationService.UpdateandCreatecalls, to confirm the write is committed rather than staged
Without the source, see the next section.
Form scripts and web resources
Script web resources are in WebResources/ as plain files, so this part is the easy one. Search them for the logical name and for the accessor shapes around it: getAttribute("creditlimit"), a .setValue( on the result, and any Xrm.WebApi call carrying the column in its payload.
HTML web resources need one extra pass, since the script is embedded in the markup rather than sitting in its own file.
Cross-reference what you find against the form definitions, because a script only runs where it is registered as an event handler. A web resource nobody has wired to a form is not a writer.
Where the method stops
Two gaps, and neither closes with more effort.
- Compiled assemblies with no source. If the plugin came from an ISV or a contractor who left, the IL is in the export and the write is in the IL, but not in a form you can read or search. You know a plugin runs on the table. You cannot tell which columns it sets.
- Late-bound writes where the column name is assembled at runtime. A plugin doing
entity[configuredFieldName] = valuehas no column literal anywhere in the file. Nothing resolves that statically, and any tool claiming to is guessing.
The honest output of this exercise is a list plus a caveat: here are the writers I found, and here are the components I could not read. Absence of a hit is a candidate to verify, not an all-clear.
What it costs
For one column on a small solution, an hour or two. On a mature environment with a few hundred components across five formats, most of a day, and you will do it again next month because the export is a snapshot. Nothing about the method scales; it just works.
The part worth internalising is the reason it is slow. There is no single search that answers the question, because the same fact is written five different ways in five different file formats, and one of them is not text.