<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Harsh Sengar</title>
    <description>The latest articles on DEV Community by Harsh Sengar (@marvelcoder).</description>
    <link>https://dev.to/marvelcoder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4124585%2Ff45d2217-5fde-4b9e-a2af-567d59009f3e.png</url>
      <title>DEV Community: Harsh Sengar</title>
      <link>https://dev.to/marvelcoder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marvelcoder"/>
    <language>en</language>
    <item>
      <title>Why Your D365 F&amp;O Automation Breaks: The Hidden Logic Behind TargetId and RootId</title>
      <dc:creator>Harsh Sengar</dc:creator>
      <pubDate>Tue, 15 Sep 2026 08:59:56 +0000</pubDate>
      <link>https://dev.to/marvelcoder/why-your-d365-fo-automation-breaks-the-hidden-logic-behind-targetid-and-rootid-4pc7</link>
      <guid>https://dev.to/marvelcoder/why-your-d365-fo-automation-breaks-the-hidden-logic-behind-targetid-and-rootid-4pc7</guid>
      <description>&lt;h2&gt;
  
  
  Why Your D365 F&amp;amp;O Automation Breaks: The Hidden Logic Behind TargetId and RootId
&lt;/h2&gt;

&lt;p&gt;If you've ever tried to automate, load-test, or replay actions in Dynamics 365 Finance &amp;amp; Operations — whether through Postman, JMeter, k6, Gatling, an RPA tool, or a custom script — you've probably hit this wall: a request that worked perfectly in one session mysteriously fails in the next, with no obvious change to your payload.&lt;/p&gt;

&lt;p&gt;This isn't specific to any one tool. It comes down to how D365 F&amp;amp;O identifies UI controls internally, and it'll bite you the same way regardless of what's sending the request.&lt;/p&gt;

&lt;p&gt;The usual suspect turns out to be two fields buried in the interaction payload: &lt;code&gt;TargetId&lt;/code&gt; and &lt;code&gt;RootId&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Symptom
&lt;/h2&gt;

&lt;p&gt;D365 F&amp;amp;O interaction requests look something like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "CommandName": "Click",&lt;br&gt;
  "RootId": "2273_0",&lt;br&gt;
  "TargetId": "2437_0",&lt;br&gt;
  "ThrottleId": "2273_0_TG"&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These &lt;code&gt;"&amp;lt;number&amp;gt;_&amp;lt;number&amp;gt;"&lt;/code&gt; style values look like they should be static, predictable identifiers. They aren't. Reuse one from a previous session, and some actions silently fail or get rejected — even though nothing else in your request changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbvbncde5uqy0mc472tl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbvbncde5uqy0mc472tl.png" alt="Rejection" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OR&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frg2jnlc7c2kk4g0xwlnn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frg2jnlc7c2kk4g0xwlnn.png" alt="Silent Fail" width="798" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Happens
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;TargetId&lt;/code&gt; and &lt;code&gt;RootId&lt;/code&gt; aren't computed by a fixed formula. They're assigned by the server &lt;em&gt;every time the form is loaded or rebuilt&lt;/em&gt;, and cached on the page as HTML attributes on each control's DOM element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;data-dyn-serverid&lt;/code&gt; → maps to &lt;code&gt;TargetId&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data-dyn-rootserverid&lt;/code&gt; → maps to &lt;code&gt;RootId&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because these are re-issued per session/render, a value captured today may already be invalid tomorrow — or even after a simple page refresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Resolve IDs at Runtime, Not Hardcode Them
&lt;/h2&gt;

&lt;p&gt;The most reliable way to get these values is to inspect the specific element first, then query it directly by its &lt;code&gt;id&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Right-click the control in the browser → &lt;strong&gt;Inspect&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Note its &lt;code&gt;id&lt;/code&gt; attribute (e.g. &lt;code&gt;LedgerJournalTransDaily_4_PostJournal&lt;/code&gt;). The numeric part in the middle (&lt;code&gt;_4_&lt;/code&gt;) is session-specific and will differ next time — that's expected.&lt;/li&gt;
&lt;li&gt;In the console, query that exact element and read its attributes:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;document.querySelector('#LedgerJournalTransDaily_4_PostJournal').getAttribute('data-dyn-serverid')   // TargetId&lt;br&gt;
document.querySelector('#LedgerJournalTransDaily_4_PostJournal').getAttribute('data-dyn-rootserverid') // RootId&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You might be tempted to skip the manual inspect step and match by the &lt;em&gt;control name&lt;/em&gt; alone (the part of the id after the session-specific number, e.g. &lt;code&gt;PostJournal&lt;/code&gt;), since that name is stable across sessions. In practice, this is less reliable than it looks — the same control name can appear more than once in a form (for example, a duplicate rendered into an overflow menu, or a hidden/inactive clone), and a generic lookup can silently grab the wrong instance and return an id that looks valid but isn't the one you clicked. Inspecting the element directly guarantees you're reading attributes off the exact control you care about.&lt;/p&gt;

&lt;p&gt;If you do want to automate this lookup rather than doing it manually each time, scope your selector to a container you know holds only one live instance of that control, and verify the result against what you see in the Network tab before trusting it.&lt;/p&gt;

&lt;p&gt;Run this right before building your request, in the same session/browser context you'll be sending it from, and use the values immediately.&lt;/p&gt;

&lt;p&gt;This inspection step is tool-agnostic by nature — it's just browser DevTools. However you plan to replay the request afterward — Postman, a load-testing tool, an RPA script, or anything else — the values need to come from this lookup each time, not from a saved or hardcoded payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Nuance Most People Miss: Not All Actions Are Equally Strict
&lt;/h2&gt;

&lt;p&gt;Here's the part that surprised me. I tested this across several actions sitting in the &lt;em&gt;same&lt;/em&gt; toolbar — including &lt;code&gt;Save&lt;/code&gt; and &lt;code&gt;Post&lt;/code&gt; on a journal form.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Save&lt;/code&gt;&lt;/strong&gt; tolerated a stale &lt;code&gt;TargetId&lt;/code&gt;. Even with an outdated id, the save request still succeeded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Post&lt;/code&gt;&lt;/strong&gt; did not. A stale &lt;code&gt;TargetId&lt;/code&gt; on a Post request was rejected outright.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two buttons live in the exact same part of the UI and are dispatched through the identical client-side mechanism — same &lt;code&gt;CommandName&lt;/code&gt; + &lt;code&gt;TargetId&lt;/code&gt; + &lt;code&gt;RootId&lt;/code&gt; structure, no special-casing in the client code for either one. So the difference isn't client-side at all — it's how strictly the server validates the control context before executing each type of action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; operations that directly mutate/commit data — like Post, which triggers real business logic with lasting side effects — require the server to resolve &lt;code&gt;TargetId&lt;/code&gt; to a currently live control before it will execute. If it can't, it rejects the request rather than guessing. Generic operations like Save don't need that same strict resolution — they just persist the form's current state, regardless of exactly which control instance triggered it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway for Anyone Automating or Load-Testing D365 F&amp;amp;O
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Never hardcode &lt;code&gt;TargetId&lt;/code&gt; / &lt;code&gt;RootId&lt;/code&gt;, no matter which tool is sending the request. Resolve them at runtime, every session.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;data-dyn-controlname&lt;/code&gt; as your stable anchor, not the DOM &lt;code&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Be extra disciplined about refreshing these values before any &lt;strong&gt;data-mutating&lt;/strong&gt; action (Post, Confirm, Invoice, etc.) — that's where stale ids will actually break your requests, not just silently get ignored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you're building a Postman collection, a JMeter/k6/Gatling load test, an RPA workflow, or a custom script against D365 F&amp;amp;O, baking this lookup into your setup step will save you a lot of "why did this randomly stop working" debugging later.&lt;/p&gt;

</description>
      <category>dynamics365</category>
      <category>webdev</category>
      <category>testing</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
