<?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: Daniel Jonathan</title>
    <description>The latest articles on DEV Community by Daniel Jonathan (@imdj).</description>
    <link>https://dev.to/imdj</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3511351%2F03a6aadb-3b26-441e-906b-83fc70af6b8f.jpg</url>
      <title>DEV Community: Daniel Jonathan</title>
      <link>https://dev.to/imdj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imdj"/>
    <language>en</language>
    <item>
      <title>Debugging XSLT vs Liquid in VS Code</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Fri, 03 Apr 2026 09:39:35 +0000</pubDate>
      <link>https://dev.to/imdj/debugging-xslt-vs-liquid-in-vs-code-32h4</link>
      <guid>https://dev.to/imdj/debugging-xslt-vs-liquid-in-vs-code-32h4</guid>
      <description>&lt;p&gt;Both the XSLT Debugger and DotLiquid Debugger let you step through a template and inspect variables. But they work differently under the hood — and those differences affect what you can do while debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fundamental Difference
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;XSLT debugging is live.&lt;/strong&gt; The XSLT Debugger supports two engines, each with its own instrumentation strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saxon (XSLT 2.0/3.0)&lt;/strong&gt; — exposes a &lt;code&gt;TraceListener&lt;/code&gt; interface with &lt;code&gt;Enter&lt;/code&gt; and &lt;code&gt;Leave&lt;/code&gt; callbacks that fire as each instruction executes. The engine cooperates natively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET &lt;code&gt;XslCompiledTransform&lt;/code&gt; (XSLT 1.0)&lt;/strong&gt; — has no TraceListener, so the debugger rewrites the stylesheet at load time, injecting &lt;code&gt;&amp;lt;dbg:probe&amp;gt;&lt;/code&gt; extension calls into every &lt;code&gt;template&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;for-each&lt;/code&gt;, and &lt;code&gt;when&lt;/code&gt; block. A registered extension object handles each probe and pauses execution on a &lt;code&gt;TaskCompletionSource&lt;/code&gt; until you click Step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches genuinely pause execution. You're inspecting a running process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Liquid debugging is replay.&lt;/strong&gt; DotLiquid has no such API — &lt;code&gt;Template.Render()&lt;/code&gt; runs the whole template and returns. The extension records a trace during that render, then lets you step through the recording. By the time you click Step, the template has already finished.&lt;/p&gt;




&lt;h2&gt;
  
  
  Capability Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;XSLT Debugger&lt;/th&gt;
&lt;th&gt;DotLiquid Debugger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Step model&lt;/td&gt;
&lt;td&gt;Live pause/resume&lt;/td&gt;
&lt;td&gt;Trace replay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breakpoints&lt;/td&gt;
&lt;td&gt;Yes — set and hit mid-execution&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backward stepping&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Always — it's just a recording&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable state&lt;/td&gt;
&lt;td&gt;Live, from the running engine&lt;/td&gt;
&lt;td&gt;Recorded snapshot at each step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modify and continue&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No — edit and re-render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conditional breakpoints&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter chain tracing&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Yes — each filter is a step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branch visibility&lt;/td&gt;
&lt;td&gt;Taken branch only&lt;/td&gt;
&lt;td&gt;Taken branch only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Re-render cost&lt;/td&gt;
&lt;td&gt;Steps are free (engine is paused)&lt;/td&gt;
&lt;td&gt;One render upfront, steps are free after&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Setting a breakpoint:&lt;/strong&gt;&lt;br&gt;
In the XSLT debugger you can set a breakpoint on a specific &lt;code&gt;&amp;lt;xsl:template&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;xsl:for-each&amp;gt;&lt;/code&gt;, hit F5, and the debugger stops there — even if that template fires 50 iterations in. You never see the first 49.&lt;/p&gt;

&lt;p&gt;In the DotLiquid debugger there are no breakpoints. You start at step 1 and click forward. For a template with many iterations you can drag the step slider to jump ahead quickly, but you can't say "stop when &lt;code&gt;item.qty &amp;gt; 10&lt;/code&gt;".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backward stepping:&lt;/strong&gt;&lt;br&gt;
The DotLiquid debugger supports backward stepping — you're just moving a cursor through a recording. The XSLT Debugger does not support step back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modifying state:&lt;/strong&gt;&lt;br&gt;
Neither debugger supports modify-and-continue. In both cases you edit the template or input and re-render from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter chain tracing:&lt;/strong&gt;&lt;br&gt;
This is where the DotLiquid debugger has an advantage. Because every filter application is recorded as a separate step, you can step through &lt;code&gt;name | Upcase | Truncate: 10 | Append: "…"&lt;/code&gt; and see the value after each filter. XSLT doesn't have filter chains — XPath functions are composed inline and there's no equivalent granularity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Difference Exists
&lt;/h2&gt;

&lt;p&gt;Both XSLT engines provide a path to genuine pause/resume — either through a native TraceListener (Saxon) or through stylesheet rewriting at load time (.NET XSLT 1.0). The key is that XSLT execution is structured: templates fire, instructions execute in sequence, and there are clear entry/exit points to hook into.&lt;/p&gt;

&lt;p&gt;DotLiquid was designed as a simple, safe rendering library. It has no extension points for interrupting execution, and its render loop is a single synchronous call with no observable mid-execution state. The replay approach is the only option available without forking the engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which Should You Use?
&lt;/h2&gt;

&lt;p&gt;If you're debugging &lt;strong&gt;XSLT maps&lt;/strong&gt; — especially complex structural transformations, &lt;code&gt;apply-templates&lt;/code&gt; logic, or recursive templates — the live debugger is significantly more powerful. Breakpoints and live state make it practical to debug templates that are hundreds of lines long. Use the &lt;code&gt;compiled&lt;/code&gt; engine for XSLT 1.0 (including inline C#); use Saxon for XSLT 2.0/3.0. The full series is covered in &lt;a href="https://dev.to/imdj/series/33862"&gt;XSLT Debugging in Logic Apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're debugging &lt;strong&gt;Liquid maps&lt;/strong&gt; — filter results, conditional branches, loop variable values — the replay model covers the common cases well. The main limitation is the absence of breakpoints; for most Liquid templates this is a minor inconvenience rather than a real blocker. For a deeper look at Liquid templates and the debugger extension, see &lt;a href="https://dev.to/imdj/series/38019"&gt;DotLiquid Debugging in Logic Apps&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>logicapps</category>
      <category>azure</category>
      <category>xslt</category>
      <category>dotliquid</category>
    </item>
    <item>
      <title>Debug DotLiquid Templates Locally with the VS Code DotLiquid Debugger</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Fri, 03 Apr 2026 09:38:12 +0000</pubDate>
      <link>https://dev.to/imdj/debug-dotliquid-templates-locally-with-the-vs-code-dotliquid-debugger-2eb3</link>
      <guid>https://dev.to/imdj/debug-dotliquid-templates-locally-with-the-vs-code-dotliquid-debugger-2eb3</guid>
      <description>&lt;h2&gt;
  
  
  The Problem We Are Solving
&lt;/h2&gt;

&lt;p&gt;Shopify’s Liquid preview is useful, but Logic Apps Standard runs DotLiquid, not Shopify Liquid.&lt;br&gt;
That means behavior can differ, so a template that looks correct in Shopify preview can still fail in Logic Apps.&lt;/p&gt;

&lt;p&gt;The default Logic App testing loop is slow: update template, execute, wait, inspect run history, repeat.&lt;br&gt;
For real B2B transforms, that guesswork is costly.&lt;/p&gt;

&lt;p&gt;This post shows how to debug DotLiquid locally in VS Code with fast feedback and runtime-accurate results.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Core Idea: Local Preview with the Exact Same Engine
&lt;/h2&gt;

&lt;p&gt;The DotLiquid Debugger VS Code extension runs your templates &lt;strong&gt;locally&lt;/strong&gt;, using the &lt;strong&gt;exact same DotLiquid 2.0.361 engine&lt;/strong&gt; that Azure Logic Apps Standard uses in production.&lt;/p&gt;

&lt;p&gt;This is the critical part: it uses the &lt;strong&gt;exact same engine&lt;/strong&gt; — not a simulation.&lt;/p&gt;

&lt;p&gt;Not a compatible implementation. The same NuGet package, the same version, the same sentence-cased filters, the same &lt;code&gt;content&lt;/code&gt; wrapping behaviour, the same integer division quirks.&lt;/p&gt;

&lt;p&gt;If it works here, your DotLiquid behavior should match Logic Apps closely, with far fewer deployment surprises.&lt;/p&gt;


&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;At a high level, the extension is a thin VS Code UI over a real .NET renderer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VS Code Extension (TypeScript)
  ├─ WebView panel (preview UI)
  ├─ Auto-refresh on save / keystroke
  └─ LiquidBackend ──► DotLiquidRenderer.dll (.NET 8)
                             NDJSON over stdin/stdout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.NET&lt;/code&gt; renderer is a long-running subprocess (kept alive for performance). It is compiled from source on first use and then kept alive for the session — so after the first warm-up, renders are nearly instant.&lt;/p&gt;

&lt;p&gt;Communication uses NDJSON (one JSON request per line, one response per line) with &lt;code&gt;id&lt;/code&gt;-paired responses, so the TypeScript side can handle concurrent requests without blocking.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Actually Get
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Live Preview
&lt;/h3&gt;

&lt;p&gt;Open any &lt;code&gt;.liquid&lt;/code&gt; file and press &lt;code&gt;Ctrl+Shift+L&lt;/code&gt; (or &lt;code&gt;Cmd+Shift+L&lt;/code&gt; on macOS). A preview panel opens beside the editor showing the rendered output in real time.&lt;/p&gt;

&lt;p&gt;The extension auto-refreshes on every save (or on every keystroke, debounced). The round-trip from editing to seeing the output is under 100ms for most templates. You still run after each change, but it happens locally and near-instantly instead of waiting on a Logic App execution.&lt;/p&gt;

&lt;p&gt;This alone removes the biggest bottleneck in Liquid development.&lt;/p&gt;

&lt;p&gt;Input data comes from a paired &lt;code&gt;.liquid.json&lt;/code&gt; file in the same folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales-order-transform.liquid
sales-order-transform.liquid.json   ← edit this with your test data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the input file doesn't exist, a banner offers to create a sample file for you.&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.amazonaws.com%2Fuploads%2Farticles%2F5y8oajwp8nx573omplb8.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.amazonaws.com%2Fuploads%2Farticles%2F5y8oajwp8nx573omplb8.png" alt=" " width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Step Debugger
&lt;/h3&gt;

&lt;p&gt;This is where everything changes.&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Debug&lt;/strong&gt; button in the preview toolbar. The template replays step by step — every &lt;code&gt;assign&lt;/code&gt;, loop iteration, &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;elsif&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt;, and output chunk gets its own step.&lt;/p&gt;

&lt;p&gt;At each step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The source line is highlighted in the editor&lt;/li&gt;
&lt;li&gt;The Variables panel shows every variable and its current value&lt;/li&gt;
&lt;li&gt;Variables not yet assigned are dimmed, so you see exactly what exists at this point in execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the slider or &lt;strong&gt;Prev/Next&lt;/strong&gt; buttons to navigate. Jump to the first or last step with &lt;code&gt;|◀&lt;/code&gt; and &lt;code&gt;▶|&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can finally see &lt;strong&gt;why&lt;/strong&gt; a value is wrong — not just that it is wrong.&lt;/p&gt;

&lt;p&gt;This turns "why is this variable wrong?" from a guessing game into a one-minute diagnosis.&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.amazonaws.com%2Fuploads%2Farticles%2Fdxafuob6yklcbyxkyjqa.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.amazonaws.com%2Fuploads%2Farticles%2Fdxafuob6yklcbyxkyjqa.png" alt=" " width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Filter Chain Tracing
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;assign&lt;/code&gt; step that uses filters shows the full chain below the debug bar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;499.9 | Times:5 → 2499.5 | DividedBy:100 → 24.995 | Round:2 → 25.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;31 filters are covered: all the math filters (&lt;code&gt;Times&lt;/code&gt;, &lt;code&gt;DividedBy&lt;/code&gt;, &lt;code&gt;Plus&lt;/code&gt;, &lt;code&gt;Minus&lt;/code&gt;, &lt;code&gt;Modulo&lt;/code&gt;, &lt;code&gt;Round&lt;/code&gt;, &lt;code&gt;Ceil&lt;/code&gt;, &lt;code&gt;Floor&lt;/code&gt;, &lt;code&gt;Abs&lt;/code&gt;, &lt;code&gt;AtLeast&lt;/code&gt;, &lt;code&gt;AtMost&lt;/code&gt;), string filters (&lt;code&gt;Upcase&lt;/code&gt;, &lt;code&gt;Downcase&lt;/code&gt;, &lt;code&gt;Capitalize&lt;/code&gt;, &lt;code&gt;Append&lt;/code&gt;, &lt;code&gt;Prepend&lt;/code&gt;, &lt;code&gt;Strip&lt;/code&gt;, &lt;code&gt;Replace&lt;/code&gt;, &lt;code&gt;Truncate&lt;/code&gt;, etc.), and array filters (&lt;code&gt;Split&lt;/code&gt;, &lt;code&gt;Join&lt;/code&gt;, &lt;code&gt;First&lt;/code&gt;, &lt;code&gt;Last&lt;/code&gt;, &lt;code&gt;Sort&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Reverse&lt;/code&gt;, &lt;code&gt;Size&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This is something you simply cannot do in Azure Logic Apps.&lt;/p&gt;

&lt;p&gt;Most calculation bugs live in filter chains — this feature isolates them instantly.&lt;/p&gt;

&lt;p&gt;This is especially useful when a chain of four or five filters produces an unexpected result — you can see exactly where the value diverged.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Condition Evaluation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;if&lt;/code&gt;, &lt;code&gt;elsif&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, &lt;code&gt;unless&lt;/code&gt;, and &lt;code&gt;when&lt;/code&gt; steps show the condition that was evaluated and a &lt;strong&gt;✓ taken&lt;/strong&gt; indicator if the branch was taken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;? if: content.priority == "HIGH"   ✓ taken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un-taken branches produce no step at all — which matches DotLiquid's execution model exactly.&lt;/p&gt;

&lt;p&gt;This makes it obvious why a branch did or did not execute.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Variable Panel and Line Map
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Variables panel&lt;/strong&gt; lists every &lt;code&gt;assign&lt;/code&gt; value with the line number where it was last set. Click any row to jump to that line in the source.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Line Map&lt;/strong&gt; links every output chunk back to the template line that produced it. Click any output region to jump to the source line. This is especially useful for templates that produce XML or JSON — you can click &lt;code&gt;"grandTotal": 336.20&lt;/code&gt; in the output and jump straight to the line that calculated it.&lt;/p&gt;

&lt;p&gt;Both panels collapse independently. When one collapses, the other expands to fill the full sidebar height.&lt;/p&gt;

&lt;p&gt;Together, these eliminate the need for "temporary debug output" hacks.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Output Formats: JSON, XML, HTML, Plain Text
&lt;/h3&gt;

&lt;p&gt;Because the extension renders raw text output, it works equally well for all four output formats Logic Apps supports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Template&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;invoice-flat.liquid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Flat JSON for downstream API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sales-order-transform.liquid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Complex JSON with B2B calculations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;order-to-xml.liquid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XML for EDI / ERP integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customer-to-xml.liquid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XML ERP customer import&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email-notification.liquid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTML order confirmation email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shipping-label.liquid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plain text packing slip&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Setup takes under 2 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;VS Code 1.85 or later&lt;/li&gt;
&lt;li&gt;.NET 8 SDK (&lt;a href="https://dotnet.microsoft.com/download" rel="noopener noreferrer"&gt;download&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify .NET:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet &lt;span class="nt"&gt;--version&lt;/span&gt;   &lt;span class="c"&gt;# should print 8.x.x or later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;

&lt;p&gt;Install from the VS Code Marketplace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; danieljonathan.dotliquid-template-debugger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or download the latest &lt;code&gt;.vsix&lt;/code&gt; from &lt;a href="https://github.com/imdj360/VSCodeDotLiquidDebugger" rel="noopener noreferrer"&gt;github.com/imdj360/VSCodeDotLiquidDebugger&lt;/a&gt; and install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ./dotliquid-template-debugger-0.5.0.vsix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also search &lt;strong&gt;DotLiquid Template Debugger for Logic Apps&lt;/strong&gt; in the VS Code Extensions panel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your First Preview
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a file &lt;code&gt;hello.liquid&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Plus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;price&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endfor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
{
  "greeting": "Hello, &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;!",
  "itemCount": &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;,
  "total": &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;hello.liquid.json&lt;/code&gt; in the same folder:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.99&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;14.50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Press &lt;code&gt;Ctrl+Shift+L&lt;/code&gt; to open the preview. You should see:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"greeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"itemCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;28.49&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, each change can be validated with a quick local run instead of rerunning the Logic App each iteration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;Debug&lt;/strong&gt; and step through to watch &lt;code&gt;total&lt;/code&gt; accumulate across the loop.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The F5 Launch Config Workflow
&lt;/h2&gt;

&lt;p&gt;For a proper development workflow — especially when working with multiple templates — add a &lt;code&gt;type: "dotliquid"&lt;/code&gt; launch configuration to your &lt;code&gt;.vscode/launch.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dotliquid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My: sales-order-transform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"template"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/templates/sales-order-transform.liquid"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now press &lt;strong&gt;F5&lt;/strong&gt; from the Run and Debug panel. The extension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opens the template in the editor&lt;/li&gt;
&lt;li&gt;Auto-detects the paired &lt;code&gt;.liquid.json&lt;/code&gt; input&lt;/li&gt;
&lt;li&gt;Opens the preview panel&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No manual reload. No "Developer: Reload Window". Just F5.&lt;/p&gt;

&lt;p&gt;This is the closest thing to a real development experience for Liquid templates.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;input&lt;/code&gt; field can be provided in launch config, but current preview execution reads the paired &lt;code&gt;&amp;lt;template&amp;gt;.liquid.json&lt;/code&gt; file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sample Templates
&lt;/h2&gt;

&lt;p&gt;Seven ready-to-use sample templates covering all output formats are available in a companion repository. Clone it and open it in VS Code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/imdj360/Xslt-Liquid-DebuggerTestFiles" rel="noopener noreferrer"&gt;Xslt-Liquid-DebuggerTestFiles&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each template has a paired &lt;code&gt;.liquid.json&lt;/code&gt; input file. Pick any &lt;strong&gt;Liquid:&lt;/strong&gt; config from the Run and Debug dropdown and press F5.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Typical Debugging Session
&lt;/h2&gt;

&lt;p&gt;Here is a real example: &lt;code&gt;grandTotal&lt;/code&gt; shows &lt;code&gt;0&lt;/code&gt; instead of &lt;code&gt;336.20&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without local debugging, you update the template, run the Logic App, wait, check output, and repeat.&lt;/p&gt;

&lt;p&gt;With the extension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;Debug&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;assign grandTotal&lt;/code&gt; and check the filter chain.&lt;/li&gt;
&lt;li&gt;See &lt;code&gt;couponAmt&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Step back to &lt;code&gt;couponDiscount&lt;/code&gt; and confirm it is &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Step back again and see &lt;code&gt;if content.couponCode == "WINTER20"&lt;/code&gt; was not taken.&lt;/li&gt;
&lt;li&gt;Check input JSON: &lt;code&gt;couponCode&lt;/code&gt; is &lt;code&gt;"winter20"&lt;/code&gt; (lowercase).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Done in minutes, with local runs between changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Local Debugging Fits
&lt;/h2&gt;

&lt;p&gt;Use local runs for fast iteration.&lt;br&gt;
Use Logic App execution for final validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recommended Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write your template in VS Code.&lt;/li&gt;
&lt;li&gt;Run locally with &lt;code&gt;.liquid.json&lt;/code&gt; input.&lt;/li&gt;
&lt;li&gt;Debug until correct.&lt;/li&gt;
&lt;li&gt;Deploy and validate in Logic Apps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Local inner loop: update → run locally → inspect → repeat.&lt;/p&gt;




&lt;p&gt;Liquid works best when you can see what it is doing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source &amp;amp; VSIX&lt;/strong&gt;: &lt;a href="https://github.com/imdj360/VSCodeDotLiquidDebugger" rel="noopener noreferrer"&gt;github.com/imdj360/VSCodeDotLiquidDebugger&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketplace&lt;/strong&gt;: &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.dotliquid-template-debugger" rel="noopener noreferrer"&gt;DotLiquid Template Debugger for Logic Apps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample Templates&lt;/strong&gt;: &lt;a href="https://github.com/imdj360/Xslt-Liquid-DebuggerTestFiles" rel="noopener noreferrer"&gt;github.com/imdj360/Xslt-Liquid-DebuggerTestFiles&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Also check out the XSLT Debugger — a sister extension for debugging XSLT transforms in Logic Apps Standard: &lt;a href="https://marketplace.visualstudio.com/items?itemName=DanielJonathan.xsltdebugger-darwin" rel="noopener noreferrer"&gt;macOS (arm64)&lt;/a&gt; · &lt;a href="https://marketplace.visualstudio.com/items?itemName=DanielJonathan.xsltdebugger-windows" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logicapps</category>
      <category>azure</category>
      <category>dotnet</category>
      <category>dotliquid</category>
    </item>
    <item>
      <title>Liquid Templates in Azure Logic Apps: What They Are and Why They Matter</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Fri, 03 Apr 2026 09:37:56 +0000</pubDate>
      <link>https://dev.to/imdj/liquid-templates-in-azure-logic-apps-what-they-are-and-why-they-matter-323e</link>
      <guid>https://dev.to/imdj/liquid-templates-in-azure-logic-apps-what-they-are-and-why-they-matter-323e</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Every Logic Apps Developer Hits
&lt;/h2&gt;

&lt;p&gt;You are building an integration on Azure Logic Apps Standard. The upstream system sends you a rich, nested JSON payload — a sales order with line items, discount codes, shipping methods, and state-specific tax rates. The downstream system expects a flat, transformed structure with calculated totals, a carrier label, and an SLA timestamp.&lt;/p&gt;

&lt;p&gt;The built-in expression language gets you partway there. But the moment you need a loop, a conditional lookup table, or a running subtotal across items, you hit a wall.&lt;/p&gt;

&lt;p&gt;That is the moment you reach for &lt;strong&gt;Liquid templates&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Liquid?
&lt;/h2&gt;

&lt;p&gt;Liquid is an open-source template language originally created by Shopify. It is designed to be safe, sandboxed, and easy to read — output is produced by mixing static text with template tags that reference data.&lt;/p&gt;

&lt;p&gt;There are two kinds of tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;firstName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;          /* output tag — renders a value */
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;       /* logic tag — controls flow */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;/* ... */&lt;/code&gt; markers above are code annotations for readability only — they are not valid Liquid syntax. DotLiquid comments use &lt;code&gt;{% comment %}...{% endcomment %}&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Variables, loops, conditionals, and filters cover most transformation needs without requiring you to write C# or JavaScript.&lt;/p&gt;




&lt;h2&gt;
  
  
  DotLiquid: The .NET Flavour Used by Logic Apps
&lt;/h2&gt;

&lt;p&gt;Azure Logic Apps Standard does not use the original Ruby Liquid gem. It uses &lt;strong&gt;DotLiquid&lt;/strong&gt;, a .NET port. This extension targets DotLiquid &lt;strong&gt;2.0.361&lt;/strong&gt;, which matches the version used by Logic Apps Standard at the time of writing.&lt;/p&gt;

&lt;p&gt;This is not a minor implementation detail. There are several behavioural differences that will catch you off guard if you are used to standard Liquid or testing with an online Liquid playground.&lt;/p&gt;




&lt;h3&gt;
  
  
  Difference 1 — Filter names are sentence-cased
&lt;/h3&gt;

&lt;p&gt;Standard Liquid uses lowercase filter names. DotLiquid uses sentence case.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Standard Liquid&lt;/th&gt;
&lt;th&gt;DotLiquid (Logic Apps)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upcase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Upcase&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;downcase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Downcase&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;divided_by&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DividedBy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;times&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Times&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;round&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Round&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;split&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Split&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;join&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Join&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sort&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;map&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Map&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;replace_first&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ReplaceFirst&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;truncate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Truncate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;strip_html&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;StripHtml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;url_encode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UrlEncode&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you use the wrong casing, the filter is silently ignored — the value passes through unchanged with no error.&lt;/p&gt;




&lt;h3&gt;
  
  
  Difference 2 — &lt;code&gt;DividedBy&lt;/code&gt; truncates when both operands are whole numbers
&lt;/h3&gt;

&lt;p&gt;This is the most common source of silent calculation errors.&lt;/p&gt;

&lt;p&gt;Both standard Liquid and DotLiquid produce an integer result when the divisor is a whole number — this is not a DotLiquid-specific quirk, it is how both implementations work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;divided_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;   /* standard Liquid: outputs 3, not 3.5 */
&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DividedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;    /* DotLiquid: outputs 3, not 3.5 */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output: &lt;code&gt;3&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To get decimal output, make the divisor a float:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DividedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output: &lt;code&gt;3.5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This matters for percentage calculations. &lt;code&gt;total | Times: taxRate | DividedBy: 100&lt;/code&gt; will truncate if the running value is a whole number — use &lt;code&gt;100.0&lt;/code&gt; instead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Subtle difference for negative numbers:&lt;/strong&gt; Standard Liquid (Ruby) uses &lt;em&gt;floor division&lt;/em&gt; (&lt;code&gt;-7 / 2 = -4&lt;/code&gt;), while DotLiquid (C#) uses &lt;em&gt;truncation&lt;/em&gt; (&lt;code&gt;-7 / 2 = -3&lt;/code&gt;). For positive numbers the results are identical, but if your data can contain negative values this divergence is worth knowing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Difference 3 — &lt;code&gt;Sort&lt;/code&gt; is case-insensitive
&lt;/h3&gt;

&lt;p&gt;Standard Liquid sorts with case-sensitive comparison — uppercase letters sort before lowercase in ASCII/ordinal order (&lt;code&gt;B&lt;/code&gt; &amp;lt; &lt;code&gt;a&lt;/code&gt;), so &lt;code&gt;["Banana", "apple"]&lt;/code&gt; would sort to &lt;code&gt;["Banana", "apple"]&lt;/code&gt;. DotLiquid sorts case-insensitively (&lt;code&gt;OrdinalIgnoreCase&lt;/code&gt;), so the same array becomes &lt;code&gt;["apple", "Banana"]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you are sorting strings and the casing matters to the sort order, be aware the result will differ from a Ruby Liquid environment.&lt;/p&gt;




&lt;h3&gt;
  
  
  Difference 4 — Date format uses .NET format strings, not strftime
&lt;/h3&gt;

&lt;p&gt;Standard Liquid uses &lt;code&gt;strftime&lt;/code&gt; format codes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"now"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"%Y-%m-%d"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DotLiquid uses .NET format strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"now"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yyyy-MM-dd"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filter name is also sentence-cased (&lt;code&gt;Date&lt;/code&gt;, not &lt;code&gt;date&lt;/code&gt;).&lt;/p&gt;




&lt;h3&gt;
  
  
  Difference 5 — Some standard filters are missing or renamed
&lt;/h3&gt;

&lt;p&gt;Some standard Liquid filters are not available in DotLiquid 2.0.361 at all; others exist but only under their sentence-cased name:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Standard Liquid filter&lt;/th&gt;
&lt;th&gt;DotLiquid equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compact&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Compact&lt;/code&gt; (available, sentence-cased)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uniq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Uniq&lt;/code&gt; (available, sentence-cased)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sort_natural&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;where&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;find&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your template relies on these, you will need to work around them with loops and assign statements.&lt;/p&gt;




&lt;h3&gt;
  
  
  Difference 6 — The &lt;code&gt;content&lt;/code&gt; wrapper
&lt;/h3&gt;

&lt;p&gt;When Logic Apps Standard invokes a Liquid transform, the input JSON is wrapped in a &lt;code&gt;content&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;actual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;payload...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if your input is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orderNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;149.99&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside your template you access it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;orderNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Miss this and every variable renders blank with no error message.&lt;/p&gt;




&lt;h3&gt;
  
  
  Quick reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;th&gt;Standard Liquid&lt;/th&gt;
&lt;th&gt;DotLiquid 2.0.361&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Filter casing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;upcase&lt;/code&gt;, &lt;code&gt;downcase&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Upcase&lt;/code&gt;, &lt;code&gt;Downcase&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integer division&lt;/td&gt;
&lt;td&gt;Truncates when divisor is integer (same behaviour)&lt;/td&gt;
&lt;td&gt;Truncates when both operands are whole numbers; floor vs truncation differs for negatives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sort order&lt;/td&gt;
&lt;td&gt;Case-sensitive&lt;/td&gt;
&lt;td&gt;Case-insensitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date format&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;strftime&lt;/code&gt; (&lt;code&gt;%Y-%m-%d&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;.NET format (&lt;code&gt;yyyy-MM-dd&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input root&lt;/td&gt;
&lt;td&gt;Direct access&lt;/td&gt;
&lt;td&gt;Wrapped in &lt;code&gt;content.*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;compact&lt;/code&gt; / &lt;code&gt;uniq&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Available (&lt;code&gt;compact&lt;/code&gt;, &lt;code&gt;uniq&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Available (&lt;code&gt;Compact&lt;/code&gt;, &lt;code&gt;Uniq&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;sort_natural&lt;/code&gt; / &lt;code&gt;where&lt;/code&gt; / &lt;code&gt;sum&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Core Syntax You Will Use Daily
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;firstName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Upcase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Assign (variables)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;taxRate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.085&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;taxRate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-&lt;/code&gt; inside &lt;code&gt;{%-&lt;/code&gt; and &lt;code&gt;-%}&lt;/code&gt; strips the surrounding whitespace — essential for keeping JSON output clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loops
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lineTotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;quantity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;unitPrice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Plus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lineTotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endfor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conditionals
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;priority&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CRITICAL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;slaHours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;elsif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;priority&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;slaHours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;slaHours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Filter chains
&lt;/h3&gt;

&lt;p&gt;Filters are chained left to right. Each filter receives the output of the previous one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;grandTotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Minus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;discount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Plus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A Real-World Example: B2B Sales Order Transform
&lt;/h2&gt;

&lt;p&gt;Here is a condensed version of a real Logic Apps transformation. The input is a B2B order with line items, a coupon code, and a shipping method. The output is a flat JSON structure ready for an ERP system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input (excerpt):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"couponCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WINTER20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shipping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FEDEX_GROUND"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HARDWARE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unitPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;24.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"discountPct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SOFTWARE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unitPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;99.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"discountPct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Template (excerpt):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;couponDiscount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;couponCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WINTER20"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;couponDiscount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;elsif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;couponCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SAVE10"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;couponDiscount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;

&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;slaHours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;priority&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;slaHours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;

&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;carrier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FedEx"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;serviceLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ground"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;

&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;grossLine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;quantity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;unitPrice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lineDiscount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;grossLine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;discountPct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DividedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;netLine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;grossLine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Minus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lineDiscount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Plus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;netLine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endfor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;

&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;couponAmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;couponDiscount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DividedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;grandTotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Minus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;couponAmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;

{
  "carrier": "&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;carrier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;",
  "serviceLevel": "&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;serviceLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;",
  "slaHours": &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;slaHours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;,
  "subtotal": &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subtotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;,
  "couponDiscount": &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;couponAmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;,
  "grandTotal": &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;grandTotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"carrier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FedEx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"serviceLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ground"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slaHours"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subtotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;407.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"couponDiscount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;81.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"grandTotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;325.8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Output Is Not Just JSON
&lt;/h2&gt;

&lt;p&gt;A common misconception is that Logic Apps Liquid transforms only produce JSON. The template engine outputs whatever text the template produces. The three other common output formats:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;XML&lt;/strong&gt; — for B2B, EDI, or ERP integrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;PurchaseOrder id="&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;orderNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;"&amp;gt;
  &amp;lt;Vendor&amp;gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&amp;lt;/Vendor&amp;gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &amp;lt;LineItem sku="&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;sku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;"&amp;gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;quantity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&amp;lt;/LineItem&amp;gt;
  &lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endfor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&amp;lt;/PurchaseOrder&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;HTML&lt;/strong&gt; — for email bodies via SendGrid or similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;Order Confirmation&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;Hi &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;firstName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;,&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Your order total is $&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;.&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Plain text&lt;/strong&gt; — for warehouse printers, SMS, or legacy systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;TRACKING: &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;trackingNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
SHIP TO:  &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
          &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Pain: Flying Blind
&lt;/h2&gt;

&lt;p&gt;Everything described above sounds straightforward — until you actually try to build it inside Logic Apps Studio.&lt;/p&gt;

&lt;p&gt;The typical development loop looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the template in the Azure Portal or a text editor&lt;/li&gt;
&lt;li&gt;Upload it to your Logic Apps integration account&lt;/li&gt;
&lt;li&gt;Deploy or trigger a test run&lt;/li&gt;
&lt;li&gt;Wait for the run history to populate&lt;/li&gt;
&lt;li&gt;Expand the transform action output&lt;/li&gt;
&lt;li&gt;Discover the output is wrong — or blank&lt;/li&gt;
&lt;li&gt;Guess which line is the problem&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no syntax highlighting. No inline error messages. No way to inspect what a variable holds mid-template. A misnamed filter (&lt;code&gt;upcase&lt;/code&gt; instead of &lt;code&gt;Upcase&lt;/code&gt;) is silently ignored — the value passes through unchanged with no warning. A wrong dot-path (&lt;code&gt;content.order.total&lt;/code&gt; instead of &lt;code&gt;content.total&lt;/code&gt;) does the same.&lt;/p&gt;

&lt;p&gt;The round-trip from "edit" to "see output" can take five minutes or more per iteration for a non-trivial template.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;In Part 2, we look at the &lt;strong&gt;DotLiquid Debugger&lt;/strong&gt; VS Code extension — a tool that replaces the Azure Portal round-trip with instant local feedback, a step-by-step debugger, and filter chain tracing, all using the same DotLiquid 2.0.361 engine that Logic Apps Standard runs in production.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 2: &lt;a href="//./part2-dotliquid-debugger-extension.md"&gt;Debug DotLiquid Templates Locally with the VS Code DotLiquid Debugger&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logicapps</category>
      <category>dotnet</category>
      <category>dotliquid</category>
      <category>azure</category>
    </item>
    <item>
      <title>Teaching Coding Agent to Write XSLT — The Hook in Action</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Sat, 28 Mar 2026 07:44:17 +0000</pubDate>
      <link>https://dev.to/imdj/teaching-claude-code-to-write-xslt-the-hook-in-action-l8n</link>
      <guid>https://dev.to/imdj/teaching-claude-code-to-write-xslt-the-hook-in-action-l8n</guid>
      <description>&lt;p&gt;Parts 1 and 2 explained the skill and the hook setup. This part shows it actually running — a real session, real prompts, real hook output. No theory.&lt;/p&gt;

&lt;p&gt;We'll build the same Order-to-Shipment transformation three ways — XSLT 1.0 with inline C#, XSLT 2.0 with Saxon, and LML compiled to XSLT 3.0 — and watch the hook verify each one, catch bugs, and drive Claude through a multi-step fix cycle. Everything below is from a single session.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: XSLT 1.0 with inline C
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The prompt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;Create an XSLT 1.0 transformation (compatible with .NET XslCompiledTransform)
that maps an Order to a Shipment. Use the existing Order.xml as input reference
and target the ShipmentOutput.xsd schema.

Requirements:
- Map OrderID → ShipmentRef with a "SHP-" prefix (use inline C# via msxsl:script)
- Map OrderDate → ShipDate, reformatted from yyyy-MM-dd to dd/MM/yyyy (inline C#)
- Map CustomerName → Recipient
- Generate an ExecutionDate using inline C# that returns DateTime.UtcNow
- Loop over each Item → Line, mapping ProductName → Product and Quantity → Quantity
- Filter out items where Quantity &lt;span class="err"&gt;&amp;lt;&lt;/span&gt; 1

Use the Order namespace `http://schemas.contoso.com/Order`.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Claude reads first
&lt;/h3&gt;

&lt;p&gt;Before writing anything, Claude reads the source and target schemas to understand both structures. This is the skill's Generate mode — it requires understanding the data shapes before choosing XPath expressions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt; — &lt;code&gt;Order.xml&lt;/code&gt; with namespace &lt;code&gt;http://schemas.contoso.com/Order&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target&lt;/strong&gt; — &lt;code&gt;ShipmentOutput.xsd&lt;/code&gt;, no namespace:&lt;/p&gt;

&lt;p&gt;Claude also reads the existing &lt;code&gt;.vscode/launch.json&lt;/code&gt; to know where to append the new debug configuration. Now it writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The write sequence
&lt;/h3&gt;

&lt;p&gt;The skill enforces a specific write order: &lt;strong&gt;launch config first, stylesheet last&lt;/strong&gt;. This ensures the debug configuration is in place before the hook fires on the &lt;code&gt;.xslt&lt;/code&gt; write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File 1: &lt;code&gt;.vscode/launch.json&lt;/code&gt;&lt;/strong&gt; — appends a new configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xslt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debug Order2Shipment_20260327"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"compiled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stylesheet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/Artifacts/Maps/Order2Shipment_20260327.xslt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"xml"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/Artifacts/SampleData/Order.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/Artifacts/Maps/out/Order2Shipment_20260327-out.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stopOnEntry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"log"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Engine is &lt;code&gt;compiled&lt;/code&gt; — the .NET &lt;code&gt;XslCompiledTransform&lt;/code&gt; engine that supports &lt;code&gt;msxsl:script&lt;/code&gt; inline C#. No hook fires on this file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File 2: &lt;code&gt;Artifacts/Maps/Order2Shipment_20260327.xslt&lt;/code&gt;&lt;/strong&gt; — the stylesheet with three C# helpers in a &lt;code&gt;msxsl:script&lt;/code&gt; block:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PrefixShipmentRef(string orderId)&lt;/code&gt; — prepends &lt;code&gt;"SHP-"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ReformatDate(string isoDate)&lt;/code&gt; — parses &lt;code&gt;yyyy-MM-dd&lt;/code&gt;, returns &lt;code&gt;dd/MM/yyyy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ExecutionDate()&lt;/code&gt; — returns &lt;code&gt;DateTime.UtcNow&lt;/code&gt; as &lt;code&gt;yyyy-MM-ddTHH:mm:ssZ&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The hook fires.&lt;/strong&gt; While Claude is still in the same response turn, the XSLT Debugger API compiles the inline C# via Roslyn, runs the transform against &lt;code&gt;Order.xml&lt;/code&gt;, and returns the result:&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.amazonaws.com%2Fuploads%2Farticles%2Fwlyki27zuyl0qzb9mnsg.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.amazonaws.com%2Fuploads%2Farticles%2Fwlyki27zuyl0qzb9mnsg.png" alt="ClaudeCodePrompt1" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the clean path. Before moving to XSLT 2.0, let's see what happens when this same map has bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the hook catches: two classes of failure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Silent data loss — the namespace bug
&lt;/h3&gt;

&lt;p&gt;XSLT's most dangerous bugs produce structurally correct output with silently missing data. No error, no warning — just empty elements.&lt;/p&gt;

&lt;p&gt;Claude drops the &lt;code&gt;ord:&lt;/code&gt; namespace prefix from one XPath:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Bug: missing namespace prefix --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Recipient&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xsl:value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"Customer/CustomerName"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Recipient&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook fires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Recipient&amp;gt;&amp;lt;/Recipient&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 0. The transform "succeeded". But &lt;code&gt;&amp;lt;Recipient&amp;gt;&lt;/code&gt; is empty. Without the hook, this ships to production and nobody notices until a downstream system complains.&lt;/p&gt;

&lt;p&gt;Claude sees the empty element, identifies the namespace mismatch (the skill's #1 debug pattern), and edits the file. &lt;strong&gt;The hook fires again on the Edit&lt;/strong&gt; — not just on the initial Write. Every change to an &lt;code&gt;.xslt&lt;/code&gt; file triggers a fresh transform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Recipient&amp;gt;&lt;/span&gt;John Doe&lt;span class="nt"&gt;&amp;lt;/Recipient&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixed. Two hook invocations, one fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hard compile failure — the C# syntax error
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fll057gx9buk3zqpituo8.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.amazonaws.com%2Fuploads%2Farticles%2Fll057gx9buk3zqpituo8.png" alt="FixIssueviaHook" width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exit code 1. No XML output at all — Roslyn rejects the C# before the XSLT engine even starts. Claude sees the error with exact line and column, adds the semicolon, hook fires again, clean output.&lt;/p&gt;

&lt;p&gt;These are two fundamentally different failure modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Exit code&lt;/th&gt;
&lt;th&gt;Catchable without hook?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Namespace mismatch&lt;/td&gt;
&lt;td&gt;Valid XML, empty values&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;No — CI pipelines see "success"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C# compile error&lt;/td&gt;
&lt;td&gt;No output, error message&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Yes — but only if you run it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The namespace bug is why the hook matters most. It catches the failures that look like success.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: XSLT 2.0 with Saxon
&lt;/h2&gt;

&lt;p&gt;Next prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;Now create the same map as XSLT 2.0 for Saxon. Create a new file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same write sequence: launch config first (engine &lt;code&gt;saxonnet&lt;/code&gt;), then the stylesheet. No &lt;code&gt;msxsl:script&lt;/code&gt; this time — XSLT 2.0 has native functions for everything the C# was doing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;XSLT 1.0 + C#&lt;/th&gt;
&lt;th&gt;XSLT 2.0 native&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fn:PrefixShipmentRef(string(ord:OrderID))&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;concat('SHP-', ord:OrderID)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fn:ReformatDate(string(ord:OrderDate))&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;format-date(xs:date(ord:OrderDate), '[D01]/[M01]/[Y0001]')&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fn:ExecutionDate()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]Z')&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The hook fires on the &lt;code&gt;.xslt&lt;/code&gt; write:&lt;/p&gt;

&lt;p&gt;Same output structure, same values. Two engines, two versions, same result — both verified by the hook automatically.&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.amazonaws.com%2Fuploads%2Farticles%2Fxz0cehuj7kvprtuzrpqn.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.amazonaws.com%2Fuploads%2Farticles%2Fxz0cehuj7kvprtuzrpqn.png" alt="ClaudeCodePrompt2" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: LML compiled to XSLT 3.0
&lt;/h2&gt;

&lt;p&gt;Now the same map authored in the Data Mapper's LML format — the visual designer's YAML source that compiles to XSLT 3.0. This is the most complex path because it has dependencies that must be created in the right order.&lt;/p&gt;

&lt;h3&gt;
  
  
  The prompt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Now create an LML for the same Order-to-Shipment mapping and test it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The dependency chain
&lt;/h3&gt;

&lt;p&gt;Claude reads the LML file and prepares the next actions&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.amazonaws.com%2Fuploads%2Farticles%2Fg7saoc8z053auexz4cop.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.amazonaws.com%2Fuploads%2Farticles%2Fg7saoc8z053auexz4cop.png" alt="ClaudeReadsLMLMD" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LML maps can reference custom extension functions defined in separate XML files. Before Claude can write the LML, it needs to create these functions. The full write sequence for LML mode is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Launch config&lt;/strong&gt; — debug configuration pointing at the &lt;em&gt;compiled&lt;/em&gt; &lt;code&gt;.xslt&lt;/code&gt; (not the &lt;code&gt;.lml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom functions&lt;/strong&gt; — &lt;code&gt;Artifacts/DataMapper/Extensions/Functions/ShipmentFunctions.xml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LML file&lt;/strong&gt; — &lt;code&gt;Artifacts/MapDefinitions/Order2Shipment_20260327-lml.lml&lt;/code&gt; (written last — the compile hook fires on this, using &lt;a href="https://www.nuget.org/packages/lml-compile" rel="noopener noreferrer"&gt;&lt;code&gt;lml-compile&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The launch config points at &lt;code&gt;Artifacts/Maps/Order2Shipment_20260327-lml.xslt&lt;/code&gt; — a file that doesn't exist yet. It will be generated when the LML compile hook fires. Engine is &lt;code&gt;saxonnet&lt;/code&gt; because LML always compiles to XSLT 3.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  The custom functions
&lt;/h3&gt;

&lt;p&gt;Claude creates three functions in &lt;code&gt;ShipmentFunctions.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;customfunctions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"prefixShipRef"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;param&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"orderId"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:string"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"concat('SHP-', $orderId)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/function&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"reformatDate"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;param&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"dateVal"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:date"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"format-date($dateVal, '[D01]/[M01]/[Y0001]')"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/function&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"executionDateTime"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"format-dateTime(...)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/function&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/customfunctions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hook fires — this is an &lt;code&gt;.xml&lt;/code&gt; file, not &lt;code&gt;.lml&lt;/code&gt; or &lt;code&gt;.xslt&lt;/code&gt;. But this file has a bug that won't surface until the LML compiles. &lt;code&gt;executionDateTime&lt;/code&gt; has zero parameters.&lt;/p&gt;

&lt;h3&gt;
  
  
  The LML file
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;$version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;$input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;XML&lt;/span&gt;
&lt;span class="na"&gt;$output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;XML&lt;/span&gt;
&lt;span class="na"&gt;$sourceSchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OrderSchema.xsd&lt;/span&gt;
&lt;span class="na"&gt;$targetSchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ShipmentOutput.xsd&lt;/span&gt;
&lt;span class="na"&gt;$sourceNamespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ns0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://schemas.contoso.com/Order&lt;/span&gt;
&lt;span class="na"&gt;Shipments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Shipment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ShipmentRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prefixShipRef(/ns0:Order/ns0:OrderID)&lt;/span&gt;
    &lt;span class="na"&gt;ShipDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reformatDate(/ns0:Order/ns0:OrderDate)&lt;/span&gt;
    &lt;span class="na"&gt;Recipient&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/ns0:Order/ns0:Customer/ns0:CustomerName&lt;/span&gt;
    &lt;span class="na"&gt;ExecutionDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;executionDateTime()&lt;/span&gt;
    &lt;span class="na"&gt;Lines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$for(/ns0:Order/ns0:Items/ns0:Item)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;$if(is-greater-or-equal(ns0:Quantity, 1))&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ns0:ProductName&lt;/span&gt;
            &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ns0:Quantity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude writes this file. The LML compile hook fires — calling &lt;a href="https://www.nuget.org/packages/lml-compile" rel="noopener noreferrer"&gt;&lt;code&gt;lml-compile&lt;/code&gt;&lt;/a&gt;, a dotnet global tool that wraps the Logic Apps SDK's &lt;code&gt;DataMapTestExecutor.GenerateXslt()&lt;/code&gt;. And now the fixing cycle begins.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fixing cycle
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;executionDateTime&lt;/code&gt; function has zero parameters — the SDK's &lt;code&gt;ReadCustomFunctionsDefinitionAsync()&lt;/code&gt; hits a &lt;code&gt;NullReferenceException&lt;/code&gt; on &lt;code&gt;parameters.Length&lt;/code&gt; and silently skips the entire XML file. All three functions become "unrecognized". Claude adds a dummy parameter, edits the LML, and the hook fires again — second error: &lt;code&gt;is-greater-or-equal&lt;/code&gt; isn't a real LML pseudofunction. Claude switches to &lt;code&gt;xpath("ns0:Quantity &amp;gt;= 1")&lt;/code&gt;, edits the LML one more time, and the hook returns clean output.&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.amazonaws.com%2Fuploads%2Farticles%2F94ork67mxnod0vvql7wh.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.amazonaws.com%2Fuploads%2Farticles%2F94ork67mxnod0vvql7wh.png" alt="FixingCycle" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three iterations, two bugs, one prompt. The compile hook drove the entire fix cycle — feeding each error back into Claude's context so it could diagnose and fix without manual intervention.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Iteration&lt;/th&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;executionDateTime&lt;/code&gt; unrecognized&lt;/td&gt;
&lt;td&gt;Zero-param SDK bug — null &lt;code&gt;parameters&lt;/code&gt; skips entire XML file&lt;/td&gt;
&lt;td&gt;Add dummy &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;is-greater-or-equal&lt;/code&gt; unrecognized&lt;/td&gt;
&lt;td&gt;Not a real LML pseudofunction — UI label vs compiler syntax&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;xpath("ns0:Quantity &amp;gt;= 1")&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Three engines, one result
&lt;/h2&gt;

&lt;p&gt;All three implementations now produce matching output from the same input:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Date logic&lt;/th&gt;
&lt;th&gt;String logic&lt;/th&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compiled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XSLT 1.0&lt;/td&gt;
&lt;td&gt;C# &lt;code&gt;DateTime.TryParse&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;C# &lt;code&gt;string.Concat&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;xsl:if test="ord:Quantity &amp;gt;= 1"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;saxonnet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XSLT 2.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;format-date(xs:date(...))&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;concat(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;xsl:if test="ord:Quantity &amp;gt;= 1"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;saxonnet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XSLT 3.0 (LML)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ef:reformatDate(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ef:prefixShipRef(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;xsl:when test="ns0:Quantity &amp;gt;= 1"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- All three produce: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ShipmentRef&amp;gt;&lt;/span&gt;SHP-ORD-1001&lt;span class="nt"&gt;&amp;lt;/ShipmentRef&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ShipDate&amp;gt;&lt;/span&gt;31/10/2025&lt;span class="nt"&gt;&amp;lt;/ShipDate&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Recipient&amp;gt;&lt;/span&gt;John Doe&lt;span class="nt"&gt;&amp;lt;/Recipient&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook verified each automatically at write time. This matters for teams migrating between engines — you can write the same map in XSLT 1.0 for BizTalk and XSLT 3.0 for Logic Apps, and verify both produce identical output in the same session.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you actually see in VS Code
&lt;/h2&gt;

&lt;p&gt;When the hook fires during a Claude response, the output appears inline in Claude's context — a &lt;code&gt;PostToolUse&lt;/code&gt; system reminder that Claude reads as part of the same turn. You don't see a separate panel or dialog.&lt;/p&gt;

&lt;p&gt;For the XSLT 1.0 clean path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Claude:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;reads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Order.xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ShipmentOutput.xsd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;launch.json&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Claude:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;writes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;launch.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Claude:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;writes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Order&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;Shipment_&lt;/span&gt;&lt;span class="mi"&gt;20260327&lt;/span&gt;&lt;span class="err"&gt;.xslt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Hook:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;PostToolUse:Write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Transform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;result:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;Shipments&amp;gt;...&amp;lt;/Shipments&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Claude:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Transform ran, output correct."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the LML fixing cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude: [writes launch.json entry]
Claude: [writes ShipmentFunctions.xml]
Claude: [writes Order2Shipment_20260327-lml.lml]
  ← Hook: PostToolUse:Write — ERROR: 'executionDateTime' unrecognized
Claude: [edits ShipmentFunctions.xml — adds dummy param]
Claude: [edits .lml — passes dummy argument]
  ← Hook: PostToolUse:Edit — ERROR: 'is-greater-or-equal' unrecognized
Claude: [edits .lml — switches to xpath()]
  ← Hook: PostToolUse:Edit — LML compiled + Transform result: &amp;lt;Shipments&amp;gt;...&amp;lt;/Shipments&amp;gt;
Claude: "Three iterations, all fixed. Output matches."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From your side, you prompted once. Claude read the schemas, created the files, hit three errors, fixed all three, and came back with a verified transform. The iteration happened between Claude and the hook.&lt;/p&gt;




&lt;h2&gt;
  
  
  The full picture
&lt;/h2&gt;

&lt;p&gt;Three parts, one system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill&lt;/strong&gt; — gives Claude the rules before it writes a line. Engine classification, version discipline, write order, debug taxonomy, LML syntax, known SDK bugs. Prevents common mistakes and provides the knowledge to diagnose uncommon ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hooks&lt;/strong&gt; — close the feedback loop after every write and edit. Compile on LML change using &lt;a href="https://www.nuget.org/packages/lml-compile" rel="noopener noreferrer"&gt;&lt;code&gt;lml-compile&lt;/code&gt;&lt;/a&gt; (a dotnet global tool that wraps the SDK compiler). Run transform on XSLT change via the Debugger HTTP API. Return results — output or error — to Claude's context automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The loop&lt;/strong&gt; — Claude writes → hook runs → Claude reads result → Claude fixes if needed → hook runs again. One prompt from you; the iteration happens between Claude and the hook.&lt;/p&gt;

&lt;p&gt;The skill and the hooks are independent — the skill works without the hooks (slower iteration, manual verification), the hooks work without the skill (no domain rules, more trial and error). Together they make a working environment where the AI can do real XSLT work reliably: catch silent namespace bugs before they reach production, fix C# compile errors in the same turn, iterate through LML compiler quirks without you lifting a finger.&lt;/p&gt;

&lt;p&gt;The result isn't "AI-generated XSLT" that you have to review and test manually. It's XSLT that was already tested — by the same system that wrote it, in the same conversation turn, against real input data.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The XSLT Debugger extension is on the VS Code Marketplace for &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-darwin" rel="noopener noreferrer"&gt;macOS&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-windows" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>xslt</category>
      <category>claudecode</category>
      <category>ai</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Teaching Coding Agent to Write XSLT — The Hook Chain</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Sat, 28 Mar 2026 07:41:16 +0000</pubDate>
      <link>https://dev.to/imdj/teaching-claude-code-to-write-xslt-the-hook-chain-1k7</link>
      <guid>https://dev.to/imdj/teaching-claude-code-to-write-xslt-the-hook-chain-1k7</guid>
      <description>&lt;p&gt;Part 1 covered the XSLT skill — domain knowledge that prevents mistakes upfront. This part covers the runtime side: &lt;strong&gt;two PostToolUse hooks&lt;/strong&gt; that automate compile-and-run so Claude sees the result of its own edits immediately.&lt;/p&gt;

&lt;p&gt;Claude writes a file → hooks fire → output (or error) appears in context → Claude fixes and repeats. When the debugger is running and a matching launch config exists, no manual runs are needed. Otherwise the hook reports what's missing and Claude can guide you.&lt;/p&gt;




&lt;h2&gt;
  
  
  PostToolUse hooks
&lt;/h2&gt;

&lt;p&gt;Claude Code hooks are shell commands that fire after tool events. &lt;code&gt;PostToolUse&lt;/code&gt; fires after every &lt;code&gt;Write&lt;/code&gt; or &lt;code&gt;Edit&lt;/code&gt; — meaning iterative fixes get immediate feedback on every change.&lt;/p&gt;

&lt;p&gt;Configure in &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PostToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Write|Edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bash .claude/hooks/generate-xslt-from-lml.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"statusMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Compiling LML + running transform..."&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bash .claude/hooks/run-xslt-after-edit.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"statusMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Running XSLT transform..."&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both hooks filter on file extension internally — the first acts only on &lt;code&gt;.lml&lt;/code&gt;, the second only on &lt;code&gt;.xslt&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hook 1: LML compile
&lt;/h2&gt;

&lt;p&gt;When Claude edits a &lt;code&gt;.lml&lt;/code&gt; file, the designer isn't involved — the compiled &lt;code&gt;.xslt&lt;/code&gt; would be stale. This hook compiles it and runs the transform in one step.&lt;/p&gt;

&lt;h3&gt;
  
  
  The compiler: &lt;code&gt;lml-compile&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The Logic Apps testing SDK includes &lt;code&gt;DataMapTestExecutor&lt;/code&gt; with a &lt;code&gt;GenerateXslt()&lt;/code&gt; method. I wrapped it as a .NET global tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; lml-compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No running Logic Apps host. No Azurite. Just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lml-compile input.lml output.xslt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The hook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;FILE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.file_path // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Only act on .lml files&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.lml&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0 &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="k"&gt;esac&lt;/span&gt;

&lt;span class="c"&gt;# Derive output path&lt;/span&gt;
&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; .lml&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;MAPS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/.."&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/Maps"&lt;/span&gt;
&lt;span class="nv"&gt;OUT_XSLT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MAPS_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.xslt"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MAPS_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Compile&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; +e
&lt;span class="nv"&gt;COMPILE_OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lml-compile &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT_XSLT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;COMPILE_EXIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$COMPILE_EXIT&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; err &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMPILE_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'{
    "hookSpecificOutput": {
      "hookEventName": "PostToolUse",
      "additionalContext": ("LML compile failed: " + $err)
    }
  }'&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Find workspace and run transform via debugger HTTP API&lt;/span&gt;
&lt;span class="c"&gt;# (walks up from file to find .vscode/launch.json)&lt;/span&gt;
&lt;span class="c"&gt;# ... matches launch config, calls POST /run-transform ...&lt;/span&gt;

jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; ctx &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": ("LML compiled + transform result:\n" + $ctx)
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If compile fails, Claude sees the error. If it succeeds and the debugger is running with a matching launch config, Claude sees the transform output in the same turn. If the debugger isn't running or no config matches, the hook reports the compile success and what's missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hook 2: Transform runner
&lt;/h2&gt;

&lt;p&gt;Fires when Claude writes or edits a &lt;code&gt;.xslt&lt;/code&gt; file directly (Generate mode).&lt;/p&gt;

&lt;p&gt;The key challenge: &lt;strong&gt;workspace derivation&lt;/strong&gt;. In hook context, &lt;code&gt;$(pwd)&lt;/code&gt; doesn't point to the project — it can resolve to &lt;code&gt;/private/tmp&lt;/code&gt;. The fix: walk up from the edited file's path to find &lt;code&gt;.vscode/launch.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EDITED_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;WORKSPACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DIR&lt;/span&gt;&lt;span class="s2"&gt;/.vscode/launch.json"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;WORKSPACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;break
  &lt;/span&gt;&lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nv"&gt;DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook then reads the matching launch config (resolving &lt;code&gt;${workspaceFolder}&lt;/code&gt;), extracts the input XML and engine, and calls the XSLT Debugger's HTTP API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;RESULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"http://127.0.0.1:&lt;/span&gt;&lt;span class="nv"&gt;$PORT&lt;/span&gt;&lt;span class="s2"&gt;/run-transform"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;stylesheet&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$STYLESHEET&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;xml&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$XML&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;engine&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$ENGINE&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is returned via &lt;code&gt;hookSpecificOutput&lt;/code&gt; so Claude sees the transform output in context.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the hooks interact
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claude writes &lt;code&gt;.xslt&lt;/code&gt;&lt;/strong&gt; (Generate mode):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude → Write .xslt → Hook 1 ignores (not .lml) → Hook 2 runs transform → result to Claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Claude writes &lt;code&gt;.lml&lt;/code&gt;&lt;/strong&gt; (LML mode):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude → Write .lml → Hook 1 compiles + runs transform → result to Claude → Hook 2 ignores (not .xslt)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.xslt&lt;/code&gt; file written by &lt;code&gt;lml-compile&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; trigger Hook 2 — only files Claude writes directly via &lt;code&gt;Write&lt;/code&gt; or &lt;code&gt;Edit&lt;/code&gt; trigger hooks.&lt;/p&gt;




&lt;h2&gt;
  
  
  The feedback loop
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude writes XSLT → hook runs transform → output in context → error? → Claude edits → hook runs again → correct output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No human in the loop for the fix cycle. Claude writes, sees the result, fixes if needed, sees the result again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shell bugs worth knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;set -e&lt;/code&gt; with &lt;code&gt;|| true&lt;/code&gt;:&lt;/strong&gt; Using &lt;code&gt;OUTPUT=$(cmd) || true&lt;/code&gt; resets &lt;code&gt;$?&lt;/code&gt; to 0 — the failure branch is unreachable. Fix: &lt;code&gt;set +e&lt;/code&gt;, capture &lt;code&gt;$?&lt;/code&gt;, then &lt;code&gt;set -e&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;grep -v&lt;/code&gt; returns exit 1 when no lines match:&lt;/strong&gt; Under &lt;code&gt;set -euo pipefail&lt;/code&gt;, this kills the hook. Append &lt;code&gt;|| true&lt;/code&gt; to the grep specifically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;$(pwd)&lt;/code&gt; in hook context:&lt;/strong&gt; Doesn't inherit the project directory. Always derive workspace from the edited file's path.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern generalises
&lt;/h2&gt;

&lt;p&gt;The pattern is: &lt;strong&gt;skill (domain rules) + hook (verify after every write) = self-correcting AI&lt;/strong&gt;. Nothing here is XSLT-specific except the domain knowledge and the tools the hooks call.&lt;/p&gt;

&lt;p&gt;The same approach works for Terraform (&lt;code&gt;terraform validate&lt;/code&gt; + &lt;code&gt;terraform plan&lt;/code&gt; after &lt;code&gt;.tf&lt;/code&gt; edits), SQL migrations, OpenAPI specs — anything where a write can be verified automatically.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The XSLT Debugger extension is on the VS Code Marketplace for &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-darwin" rel="noopener noreferrer"&gt;macOS&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-windows" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>xslt</category>
      <category>claudecode</category>
      <category>vscode</category>
      <category>ai</category>
    </item>
    <item>
      <title>Teaching Coding Agent to Write XSLT — Building a Domain Skill</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Sat, 28 Mar 2026 07:18:22 +0000</pubDate>
      <link>https://dev.to/imdj/teaching-coding-agent-to-write-xslt-building-a-domain-skill-138c</link>
      <guid>https://dev.to/imdj/teaching-coding-agent-to-write-xslt-building-a-domain-skill-138c</guid>
      <description>&lt;p&gt;XSLT is unforgiving. Small mistakes — a wrong namespace, a version mismatch, an engine-specific function — produce silent failures. AI assistants make the same mistakes without explicit guidance.&lt;/p&gt;

&lt;p&gt;The fix isn't a smarter model. It's &lt;strong&gt;giving the model a playbook&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Claude Code supports custom skills — markdown files that load into context when a task matches. I built one that encodes real XSLT expertise: five work modes, engine detection, version discipline, and structured output you can debug immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a Claude Code skill?
&lt;/h2&gt;

&lt;p&gt;A skill is a markdown file in &lt;code&gt;.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;. When Claude detects a matching task, it reads the skill and follows its instructions. Reference files alongside it provide deeper detail on demand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.claude/skills/xslt/
├── SKILL.md                        # modes, rules, output spec
└── references/
    ├── lml.md                      # Logic Apps Mapping Language
    ├── logicapps-xslt.md           # Logic Apps deployment and artifacts
    ├── saxoncs-quirks.md           # SaxonCS / XSLT 3.0 gotchas
    ├── biztalk-xslt.md             # BizTalk map authoring
    └── migration.md                # version migration guide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skill built in this post is available at &lt;a href="https://github.com/imdj360/dev-skills" rel="noopener noreferrer"&gt;imdj360/dev-skills&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mode detection
&lt;/h2&gt;

&lt;p&gt;The skill first classifies the task into one of five modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;XML samples, schemas, "write me an XSLT"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debug&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Broken XSLT, error message, wrong output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Migrate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Convert to 3.0", "rewrite as 1.0"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compatibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Will this run in BizTalk?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LML&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.lml&lt;/code&gt; file, Data Mapper, "compile to XSLT"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Generate and LML produce an output bundle. Generate writes up to three artifacts (debug config, sample input, stylesheet). LML writes up to four core artifacts (debug config, sample input, LML source, compiled XSLT) — plus schemas and custom functions if the map needs them. The other modes produce artifacts only when needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Engine classification
&lt;/h2&gt;

&lt;p&gt;This is where most AI-generated XSLT fails. The skill forces engine classification before any code is written:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BizTalk / XslCompiledTransform / msxsl:script&lt;/strong&gt; → XSLT 1.0, &lt;code&gt;compiled&lt;/code&gt; engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logic Apps (Transform XML action)&lt;/strong&gt; → XSLT 1.0 by default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saxon / SaxonCS&lt;/strong&gt; → XSLT 2.0 or 3.0, &lt;code&gt;saxonnet&lt;/code&gt; engine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If ambiguous, Claude must ask. It never assumes. This single rule prevents the most common failure: writing XSLT 2.0 features in a file that &lt;code&gt;.NET XslCompiledTransform&lt;/code&gt; will choke on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Version discipline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Never auto-upgrade.&lt;/strong&gt; If the stylesheet says &lt;code&gt;version="1.0"&lt;/code&gt;, output stays 1.0. No sneaking in &lt;code&gt;xsl:function&lt;/code&gt;, &lt;code&gt;matches()&lt;/code&gt;, or &lt;code&gt;xsl:sequence&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For 1.0, the skill enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;xsl:for-each&lt;/code&gt; only — no &lt;code&gt;xsl:for-each-group&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contains()&lt;/code&gt;, &lt;code&gt;substring()&lt;/code&gt;, &lt;code&gt;translate()&lt;/code&gt; — not &lt;code&gt;matches()&lt;/code&gt;, &lt;code&gt;replace()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Named templates, not &lt;code&gt;xsl:function&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Inline C# for XSLT 1.0
&lt;/h3&gt;

&lt;p&gt;Pure XSLT 1.0 has gaps — no regex, no date formatting. The &lt;code&gt;msxsl:script&lt;/code&gt; extension fills them with inline C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;msxsl:script&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"C#"&lt;/span&gt; &lt;span class="na"&gt;implements-prefix=&lt;/span&gt;&lt;span class="s"&gt;"fn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;![CDATA[
    public string ReformatDate(string isoDate) {
      if (System.DateTime.TryParse(isoDate, out System.DateTime dt))
        return dt.ToString("dd/MM/yyyy");
      return isoDate;
    }
  ]]&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/msxsl:script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skill knows the type mappings (XPath numbers → &lt;code&gt;double&lt;/code&gt;, node-sets → &lt;code&gt;XPathNodeIterator&lt;/code&gt;) and the constraint: &lt;code&gt;msxsl:script&lt;/code&gt; runs on &lt;code&gt;.NET XslCompiledTransform&lt;/code&gt; only, never Saxon.&lt;/p&gt;




&lt;h2&gt;
  
  
  Canonical skeletons
&lt;/h2&gt;

&lt;p&gt;The skill provides starting templates so Claude doesn't construct structure from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;XSLT 1.0 + inline C#:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;xsl:stylesheet&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:xsl=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/1999/XSL/Transform"&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:msxsl=&lt;/span&gt;&lt;span class="s"&gt;"urn:schemas-microsoft-com:xslt"&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:fn=&lt;/span&gt;&lt;span class="s"&gt;"urn:my-functions"&lt;/span&gt;
  &lt;span class="na"&gt;exclude-result-prefixes=&lt;/span&gt;&lt;span class="s"&gt;"msxsl fn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xsl:output&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"xml"&lt;/span&gt; &lt;span class="na"&gt;indent=&lt;/span&gt;&lt;span class="s"&gt;"yes"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- msxsl:script block, then templates --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/xsl:stylesheet&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;XSLT 3.0 / SaxonCS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;xsl:stylesheet&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"3.0"&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:xsl=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/1999/XSL/Transform"&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:xs=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema"&lt;/span&gt;
  &lt;span class="na"&gt;exclude-result-prefixes=&lt;/span&gt;&lt;span class="s"&gt;"#all"&lt;/span&gt;
  &lt;span class="na"&gt;expand-text=&lt;/span&gt;&lt;span class="s"&gt;"yes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xsl:output&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"xml"&lt;/span&gt; &lt;span class="na"&gt;indent=&lt;/span&gt;&lt;span class="s"&gt;"yes"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xsl:mode&lt;/span&gt; &lt;span class="na"&gt;on-no-match=&lt;/span&gt;&lt;span class="s"&gt;"shallow-skip"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- templates --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/xsl:stylesheet&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LML-generated XSLT 3.0 uses different conventions (&lt;code&gt;mode="azure.workflow.datamapper"&lt;/code&gt;, explicit namespace exclusions). The skill tracks both so Claude doesn't confuse them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debug taxonomy
&lt;/h2&gt;

&lt;p&gt;For Debug mode, Claude classifies the bug before attempting a fix:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Danger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Namespace mismatch&lt;/strong&gt; — empty output, XPath returns nothing&lt;/td&gt;
&lt;td&gt;High — exit code 0, looks like success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;C# compile error&lt;/strong&gt; — Roslyn error in &lt;code&gt;msxsl:script&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Obvious — exit code 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Template priority conflict&lt;/strong&gt; — wrong template fires&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Version mismatch&lt;/strong&gt; — works in Saxon, fails in .NET&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Namespace mismatch is the most dangerous: the transform succeeds with valid XML structure but every value is empty.&lt;/p&gt;




&lt;h2&gt;
  
  
  The output bundle
&lt;/h2&gt;

&lt;p&gt;For Generate and LML modes, the skill requires a complete output bundle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generate mode&lt;/strong&gt; writes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Launch config — appended to &lt;code&gt;.vscode/launch.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sample input XML — if none exists for the source schema&lt;/li&gt;
&lt;li&gt;Stylesheet — the &lt;code&gt;.xslt&lt;/code&gt; file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;LML mode&lt;/strong&gt; writes (order matters):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Source and target schemas — XSD files in &lt;code&gt;Artifacts/Schemas/&lt;/code&gt;, if not already present&lt;/li&gt;
&lt;li&gt;Launch config — points at the compiled &lt;code&gt;.xslt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Custom extension functions — if the map uses them (must be written before LML compiles)&lt;/li&gt;
&lt;li&gt;Sample input XML — if none exists for the source schema&lt;/li&gt;
&lt;li&gt;LML file — written last (compile hook fires on this)&lt;/li&gt;
&lt;li&gt;Compiled XSLT — generated automatically by the compile hook&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A launch config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debug Order2Shipment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xslt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"saxonnet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stylesheet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/Artifacts/Maps/Order2Shipment.xslt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"xml"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/Artifacts/SampleData/Order.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/Artifacts/Maps/out/Order2Shipment-out.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stopOnEntry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;output&lt;/code&gt; is optional — omit it to see results in the Debug Console only. Press F5 and debug with breakpoints — no manual setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  LML mode: custom functions and gotchas
&lt;/h2&gt;

&lt;p&gt;LML maps can call custom functions in &lt;code&gt;Artifacts/DataMapper/Extensions/Functions/*.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;customfunctions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"reformatDate"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;param&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"dateVal"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:date"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"format-date($dateVal, '[D01]/[M01]/[Y0001]')"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/function&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/customfunctions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skill documents SDK bugs Claude needs to work around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-parameter functions crash the compiler&lt;/strong&gt; — &lt;code&gt;NullReferenceException&lt;/code&gt;, entire XML file silently skipped. Workaround: add a dummy &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-step paths in function arguments fail&lt;/strong&gt; — wrap in &lt;code&gt;xpath()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bare &lt;code&gt;@attr&lt;/code&gt; is invalid YAML&lt;/strong&gt; — use &lt;code&gt;xpath("@lineNum")&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What the skill doesn't do
&lt;/h2&gt;

&lt;p&gt;The skill is domain knowledge and rules. It doesn't run anything — that's the job of hooks (Part 2). It doesn't validate XSLT at runtime — that's the debugger.&lt;/p&gt;

&lt;p&gt;Its value is &lt;strong&gt;preventing mistakes before they happen&lt;/strong&gt;. Part 2 covers the hooks that catch errors at write time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The XSLT Debugger extension is on the VS Code Marketplace for &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-darwin" rel="noopener noreferrer"&gt;macOS&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-windows" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>xslt</category>
      <category>ai</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Skip the Designer — Editing Logic Apps Data Mapper LML Files Directly</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Fri, 27 Mar 2026 20:15:23 +0000</pubDate>
      <link>https://dev.to/imdj/skip-the-designer-editing-logic-apps-data-mapper-lml-files-directly-40l0</link>
      <guid>https://dev.to/imdj/skip-the-designer-editing-logic-apps-data-mapper-lml-files-directly-40l0</guid>
      <description>&lt;p&gt;The Azure Logic Apps Data Mapper has a visual designer in VS Code. You drag lines between schemas, drop in functions, and it generates an &lt;code&gt;.lml&lt;/code&gt; file (YAML) that compiles to XSLT 3.0.&lt;/p&gt;

&lt;p&gt;In theory, you never touch the YAML. In practice, the designer has reliability problems that make direct LML editing the better workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why skip the designer?
&lt;/h2&gt;

&lt;p&gt;The designer doesn't always persist changes correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Changes revert on reopen&lt;/strong&gt; — save, close, reopen, and the &lt;code&gt;.lml&lt;/code&gt; on disk has the old value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String literals lose inner quotes&lt;/strong&gt; — &lt;code&gt;xpath("'kg'")&lt;/code&gt; becomes &lt;code&gt;xpath("kg")&lt;/code&gt;, changing a literal to an element reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expressions get corrupted&lt;/strong&gt; — conditions like &lt;code&gt;xpath("if (...) then 'Y' else 'N'")&lt;/code&gt; lose quote characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Function arguments get rewritten&lt;/strong&gt; — argument order or paths change silently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No undo across sessions&lt;/strong&gt; — you need &lt;code&gt;git diff&lt;/code&gt; to catch silent corruption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These happen with normal operations, not edge cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  What LML looks like
&lt;/h2&gt;

&lt;p&gt;LML is YAML. Here's a complete map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;$version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;$input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;XML&lt;/span&gt;
&lt;span class="na"&gt;$output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;XML&lt;/span&gt;
&lt;span class="na"&gt;$sourceSchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OrderSchema.xsd&lt;/span&gt;
&lt;span class="na"&gt;$targetSchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ShipmentOutput.xsd&lt;/span&gt;
&lt;span class="na"&gt;$sourceNamespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ns0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://schemas.contoso.com/Order&lt;/span&gt;
&lt;span class="na"&gt;$targetNamespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;xs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://www.w3.org/2001/XMLSchema&lt;/span&gt;
&lt;span class="na"&gt;Shipments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Shipment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ShipmentRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prefixShipRef(/ns0:Order/ns0:OrderID)&lt;/span&gt;
    &lt;span class="na"&gt;ShipDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reformatDate(/ns0:Order/ns0:OrderDate)&lt;/span&gt;
    &lt;span class="na"&gt;Recipient&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/ns0:Order/ns0:Customer/ns0:CustomerName&lt;/span&gt;
    &lt;span class="na"&gt;Lines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$for(/ns0:Order/ns0:Items/ns0:Item)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;$if(xpath("ns0:Quantity &amp;gt;= 1"))&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ns0:ProductName&lt;/span&gt;
            &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ns0:Quantity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Header declares schemas and namespaces. Body maps target elements (left) to source expressions (right).&lt;/p&gt;




&lt;h2&gt;
  
  
  LML syntax cheat sheet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Direct mapping&lt;/span&gt;
&lt;span class="na"&gt;Recipient&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/ns0:Order/ns0:Customer/ns0:CustomerName&lt;/span&gt;

&lt;span class="c1"&gt;# Attribute on parent element&lt;/span&gt;
&lt;span class="na"&gt;$@shipId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/ns0:Order/ns0:OrderID&lt;/span&gt;

&lt;span class="c1"&gt;# Loop over repeating elements (paths inside are relative)&lt;/span&gt;
&lt;span class="na"&gt;$for(/ns0:Order/ns0:Items/ns0:Item)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ns0:ProductName&lt;/span&gt;

&lt;span class="c1"&gt;# Conditional&lt;/span&gt;
&lt;span class="s"&gt;$if(xpath("ns0:Quantity &amp;gt;= 1"))&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ns0:ProductName&lt;/span&gt;

&lt;span class="c1"&gt;# Custom function (defined in Functions/*.xml)&lt;/span&gt;
&lt;span class="na"&gt;ShipDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reformatDate(/ns0:Order/ns0:OrderDate)&lt;/span&gt;

&lt;span class="c1"&gt;# Raw XPath escape hatch&lt;/span&gt;
&lt;span class="na"&gt;Priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xpath("upper-case(@priority)")&lt;/span&gt;
&lt;span class="na"&gt;WeightUnit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xpath("'kg'")&lt;/span&gt;   &lt;span class="c1"&gt;# inner quotes = string literal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Custom extension functions
&lt;/h2&gt;

&lt;p&gt;Defined in &lt;code&gt;Artifacts/DataMapper/Extensions/Functions/*.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;customfunctions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"reformatDate"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:string"&lt;/span&gt;
            &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Reformats yyyy-MM-dd to dd/MM/yyyy."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;param&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"dateVal"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"xs:date"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"format-date($dateVal, '[D01]/[M01]/[Y0001]')"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/function&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/customfunctions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gotchas
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Zero-parameter functions crash the compiler.&lt;/strong&gt; The SDK throws a &lt;code&gt;NullReferenceException&lt;/code&gt; and silently skips the entire XML file. Workaround: add a dummy parameter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Function arguments must be single-step element names.&lt;/strong&gt; Multi-step paths cause compile errors — wrap them in &lt;code&gt;xpath()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# INVALID&lt;/span&gt;
&lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;formatAddress(ns0:Customer/ns0:Street, ns0:Customer/ns0:City)&lt;/span&gt;

&lt;span class="c1"&gt;# FIX&lt;/span&gt;
&lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;formatAddress(xpath("ns0:Customer/ns0:Street"), xpath("ns0:Customer/ns0:City"))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The workflow: edit, compile, verify
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Edit the YAML
&lt;/h3&gt;

&lt;p&gt;Open the &lt;code&gt;.lml&lt;/code&gt; file in any text editor and make your change.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Compile to XSLT
&lt;/h3&gt;

&lt;p&gt;Install &lt;a href="https://www.nuget.org/packages/lml-compile" rel="noopener noreferrer"&gt;&lt;code&gt;lml-compile&lt;/code&gt;&lt;/a&gt;, a dotnet global tool that wraps the Logic Apps SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; lml-compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lml-compile Artifacts/MapDefinitions/MyMap-lml.lml Artifacts/Maps/MyMap-lml.xslt
&lt;span class="c"&gt;# OK: MyMap-lml.xslt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Verify
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;dotnet test&lt;/code&gt; or press F5 with the &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-darwin" rel="noopener noreferrer"&gt;XSLT Debugger&lt;/a&gt; extension.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auto-compile on save
&lt;/h2&gt;

&lt;p&gt;Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave" rel="noopener noreferrer"&gt;Run on Save&lt;/a&gt; extension and add this to your workspace settings (for multi-root workspaces, add it in the &lt;code&gt;.code-workspace&lt;/code&gt; file — Run on Save doesn't read folder-level settings):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"emeraldwalk.runonsave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"commands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.lml$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lml-compile ${file} ${fileDirname}/../Maps/${fileBasenameNoExt}.xslt"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Run on Save uses its own variables — not VS Code task variables. &lt;code&gt;${fileBasenameNoExt}&lt;/code&gt; and &lt;code&gt;${fileDirname}&lt;/code&gt; work; &lt;code&gt;${fileBasenameNoExtension}&lt;/code&gt; and &lt;code&gt;${workspaceFolder}&lt;/code&gt; do not. Reload the window after adding the config.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the cycle is just: &lt;strong&gt;edit &lt;code&gt;.lml&lt;/code&gt; → save → F5 or &lt;code&gt;dotnet test&lt;/code&gt;&lt;/strong&gt;. The compile happens automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the compiler catches
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Malformed YAML / missing &lt;code&gt;$version&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;Missing schemas in &lt;code&gt;Artifacts/Schemas/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Unrecognized function calls&lt;/li&gt;
&lt;li&gt;Invalid XPath syntax&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Errors surface on save instead of at deploy time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Naming convention
&lt;/h2&gt;

&lt;p&gt;Name hand-authored LML files with a &lt;code&gt;-lml&lt;/code&gt; suffix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Artifacts/MapDefinitions/OrderToShipment-lml.lml  →  Artifacts/Maps/OrderToShipment-lml.xslt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids colliding with hand-authored XSLT files and makes it clear which files are compiled output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Use the designer to scaffold the initial map. Then switch to direct LML editing for all subsequent changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edit .lml → save → auto-compile → verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No designer round-trip. No silent rewrites. No surprises.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The XSLT Debugger extension is on the VS Code Marketplace for &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-darwin" rel="noopener noreferrer"&gt;macOS&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-windows" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logicapps</category>
      <category>lml</category>
      <category>datamapper</category>
      <category>xslt</category>
    </item>
    <item>
      <title>Workaround: Testing Logic Apps Data Mapper Maps on macOS</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Fri, 27 Mar 2026 19:50:25 +0000</pubDate>
      <link>https://dev.to/imdj/workaround-testing-logic-apps-data-mapper-maps-on-macos-16k8</link>
      <guid>https://dev.to/imdj/workaround-testing-logic-apps-data-mapper-maps-on-macos-16k8</guid>
      <description>&lt;p&gt;The Logic Apps Data Mapper in VS Code lets you create XML transformation maps on macOS. You can drag connections between source and target schemas, add functions, save — the visual designer works. The &lt;code&gt;.lml&lt;/code&gt; file gets written to disk. You can even compile it to XSLT 3.0.&lt;/p&gt;

&lt;p&gt;But you can't test it.&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.amazonaws.com%2Fuploads%2Farticles%2Ffxyxbrxhlbhnv04kga3b.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.amazonaws.com%2Fuploads%2Farticles%2Ffxyxbrxhlbhnv04kga3b.png" alt="Mapping" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click &lt;strong&gt;Test Map&lt;/strong&gt; on macOS, you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;400 – BadRequest: The .NET framework worker could not be found.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Test Map feature depends on the Azure Functions .NET Framework worker — a Windows-only component. The button is there, the backend isn't. You can create maps all day — you just can't verify they produce correct output without deploying to Azure or switching to a Windows machine.&lt;/p&gt;

&lt;p&gt;This post shows how to compile and run Data Mapper maps locally on any platform using the Logic Apps testing SDK and standard .NET unit tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  The options
&lt;/h2&gt;

&lt;p&gt;When you can't use Test Map, you have three paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to Azure and test there&lt;/strong&gt; — works, but slow and requires infrastructure for every iteration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start &lt;code&gt;func host start&lt;/code&gt; locally with Azurite&lt;/strong&gt;, call the REST compile endpoint — heavyweight, requires Azurite and a warm Azure Functions host, flaky on macOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;DataMapTestExecutor&lt;/code&gt; from the testing SDK&lt;/strong&gt; — no host, no Azurite, no Azure, just &lt;code&gt;dotnet test&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Option 3 is what this post covers.&lt;/p&gt;




&lt;h2&gt;
  
  
  DataMapTestExecutor
&lt;/h2&gt;

&lt;p&gt;The Logic Apps testing SDK (&lt;code&gt;Microsoft.Azure.Workflows.WebJobs.Tests.Extension&lt;/code&gt; v1.0.1) includes a class called &lt;code&gt;DataMapTestExecutor&lt;/code&gt;. It does two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compiles LML to XSLT&lt;/strong&gt; — &lt;code&gt;GenerateXslt(mapName)&lt;/code&gt; reads the &lt;code&gt;.lml&lt;/code&gt; from &lt;code&gt;Artifacts/MapDefinitions/&lt;/code&gt;, resolves schemas, custom functions, and inline XSLT snippets, and returns the compiled XSLT 3.0 as bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs the transform&lt;/strong&gt; — &lt;code&gt;RunMapAsync(xslt, input)&lt;/code&gt; takes the compiled XSLT and input XML bytes, executes the transform via SaxonCS, and returns the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the same compilation and execution pipeline that the designer's Test Map button and the &lt;code&gt;func host&lt;/code&gt; runtime use — you're running the same code path, just without the Azure Functions host scaffolding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up the test project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Project structure
&lt;/h3&gt;

&lt;p&gt;The test project lives alongside the Logic App project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MyWorkspace/
├── MyLogicApp/                     # Logic App project (Standard)
│   ├── Artifacts/
│   │   ├── MapDefinitions/         # .lml source files
│   │   ├── Maps/                   # compiled .xslt (generated)
│   │   ├── Schemas/                # XSD / JSON schemas
│   │   ├── SampleData/             # sample input XML
│   │   └── DataMapper/
│   │       └── Extensions/
│   │           ├── Functions/      # custom function XML files
│   │           └── InlineXslt/     # inline XSLT snippets
│   └── .vscode/
└── Tests/
    └── MyLogicApp.Tests/           # test project
        ├── MyLogicApp.Tests.csproj
        └── OrderToShipment/
            ├── OrderToShipmentTest.cs
            └── Order-input.xml     # test-specific input data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;.csproj&lt;/code&gt;
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fqdaexjbwevukzwvn2cr6.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.amazonaws.com%2Fuploads%2Farticles%2Fqdaexjbwevukzwvn2cr6.png" alt="PackageReference" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The critical dependency is &lt;code&gt;Microsoft.Azure.Workflows.WebJobs.Tests.Extension&lt;/code&gt; &lt;strong&gt;v1.0.1&lt;/strong&gt; — not v1.0.0. The &lt;code&gt;DataMapTestExecutor&lt;/code&gt; class doesn't exist in v1.0.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup: project root, compilation, and result parsing
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;DataMapTestExecutor&lt;/code&gt; needs the Logic App project root — the directory that contains &lt;code&gt;Artifacts/&lt;/code&gt;. It scans from there to find schemas, custom functions, and map definitions. &lt;code&gt;[TestInitialize]&lt;/code&gt; runs before each test method — it compiles the LML to XSLT so every test starts from a freshly compiled stylesheet.&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.amazonaws.com%2Fuploads%2Farticles%2Fk8vfgnsq0widi46oo6z7.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.amazonaws.com%2Fuploads%2Farticles%2Fk8vfgnsq0widi46oo6z7.png" alt="Initialize" width="800" height="745"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ProjectRoot&lt;/code&gt; path is relative from the test's output directory — the number of &lt;code&gt;..&lt;/code&gt; segments depends on your folder structure. Count from &lt;code&gt;bin/Debug/net8.0/&lt;/code&gt; up to wherever the Logic App project lives.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GenerateXslt&lt;/code&gt; takes the map name without path or extension. It finds the matching &lt;code&gt;.lml&lt;/code&gt; in &lt;code&gt;Artifacts/MapDefinitions/&lt;/code&gt;, reads the schemas from the LML header, loads custom functions, and compiles to XSLT 3.0.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RunMapAsync&lt;/code&gt; returns a &lt;code&gt;JToken&lt;/code&gt; with the transform output encoded as base64:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"$content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"$content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PFNoaXBtZW50cy4uLg=="&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A helper to decode it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;XDocument&lt;/span&gt; &lt;span class="nf"&gt;ParseResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JToken&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"$content"&lt;/span&gt;&lt;span class="p"&gt;]!.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()!;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;XDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Map 1: Order to Shipment — simple field mappings and filtering
&lt;/h2&gt;

&lt;p&gt;This map transforms a single Order into a Shipment. It tests custom extension functions (&lt;code&gt;prefixShipRef&lt;/code&gt;, &lt;code&gt;reformatDate&lt;/code&gt;, &lt;code&gt;executionDateTime&lt;/code&gt;), direct field mappings, a &lt;code&gt;$for&lt;/code&gt; loop over line items, and a &lt;code&gt;$if&lt;/code&gt; filter that excludes items with &lt;code&gt;Quantity &amp;lt; 1&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing individual fields
&lt;/h3&gt;

&lt;p&gt;Each mapping requirement gets its own test for clear failure diagnostics:&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.amazonaws.com%2Fuploads%2Farticles%2F232i6gogsvsajzskipty.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.amazonaws.com%2Fuploads%2Farticles%2F232i6gogsvsajzskipty.png" alt="Image33" width="800" height="828"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Validating the entire output
&lt;/h3&gt;

&lt;p&gt;One test that checks everything — structure, values, element order, and the filter:&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.amazonaws.com%2Fuploads%2Farticles%2Flz3obx7e7ka1l547odi4.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.amazonaws.com%2Fuploads%2Farticles%2Flz3obx7e7ka1l547odi4.png" alt="CompleteMapping" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Map 2: PurchaseOrders to ShippingManifest — attributes, nested loops, computed values
&lt;/h2&gt;

&lt;p&gt;This is a more complex map. It transforms multiple PurchaseOrders into a ShippingManifest with XML attributes, nested &lt;code&gt;$for&lt;/code&gt; loops (Orders → Items), computed aggregates (&lt;code&gt;totalWeight&lt;/code&gt;, &lt;code&gt;totalValue&lt;/code&gt;), and custom functions (&lt;code&gt;shipId&lt;/code&gt;, &lt;code&gt;formatAddress&lt;/code&gt;, &lt;code&gt;hazardousFlag&lt;/code&gt;, &lt;code&gt;parcelSubtotal&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The test class follows the same pattern as Map 1 — same setup, different map name and input file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing attributes and computed values
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2F2qqujloejvh4ga2w4xcu.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.amazonaws.com%2Fuploads%2Farticles%2F2qqujloejvh4ga2w4xcu.png" alt="Image2" width="800" height="815"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing nested loop output with hazmat flag
&lt;/h3&gt;

&lt;p&gt;The inner &lt;code&gt;$for&lt;/code&gt; loops over each Order's Items. The &lt;code&gt;hazardousFlag&lt;/code&gt; custom function maps &lt;code&gt;@hazardous="true"&lt;/code&gt; to &lt;code&gt;"Y"&lt;/code&gt;:&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.amazonaws.com%2Fuploads%2Farticles%2Flkmgnxfdsjapdwn9y99w.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.amazonaws.com%2Fuploads%2Farticles%2Flkmgnxfdsjapdwn9y99w.png" alt="Image3" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the tests
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2Fthsndjj6b2y6wuzi0m4j.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.amazonaws.com%2Fuploads%2Farticles%2Fthsndjj6b2y6wuzi0m4j.png" alt="UnitTestingExecution" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What DataMapTestExecutor does under the hood
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataMapTestExecutor(projectRoot)
  → scans Artifacts/Schemas/ for XSD and JSON schemas
  → scans Artifacts/DataMapper/Extensions/Functions/*.xml for custom functions
  → scans Artifacts/DataMapper/Extensions/InlineXslt/*.xml for XSLT snippets

GenerateXslt(mapName)
  → reads Artifacts/MapDefinitions/{mapName}.lml
  → resolves schema references from the LML header
  → compiles LML → XSLT 3.0 via DataMapperUtility
  → returns byte[] of the compiled stylesheet

RunMapAsync(xslt, input)
  → loads the XSLT via SaxonCS (bundled in the SDK)
  → runs the transform against the input bytes
  → returns JToken with base64-encoded XML result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Known gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;v1.0.0 vs v1.0.1.&lt;/strong&gt; &lt;code&gt;DataMapTestExecutor&lt;/code&gt; doesn't exist in v1.0.0 of the SDK. If &lt;code&gt;dotnet test&lt;/code&gt; fails with a missing type, check your &lt;code&gt;Microsoft.Azure.Workflows.WebJobs.Tests.Extension&lt;/code&gt; version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ProjectRoot must point to the Logic App root.&lt;/strong&gt; The path must resolve to the directory containing &lt;code&gt;Artifacts/&lt;/code&gt; — not the test project, not the solution root. If &lt;code&gt;GenerateXslt&lt;/code&gt; throws "map not found", your path is wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-parameter custom functions.&lt;/strong&gt; If any &lt;code&gt;&amp;lt;function&amp;gt;&lt;/code&gt; in &lt;code&gt;Functions/*.xml&lt;/code&gt; has no &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; elements, the SDK hits a &lt;code&gt;NullReferenceException&lt;/code&gt; on &lt;code&gt;function.parameters.Length&lt;/code&gt;. The entire XML file is silently skipped, making all functions in it "unrecognized". Workaround: add a dummy parameter &lt;code&gt;&amp;lt;param name="_unused" as="xs:string"/&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom function namespace scoping.&lt;/strong&gt; Function files are parsed in isolation. Namespace prefixes declared in the main stylesheet (like &lt;code&gt;xmlns:tns="..."&lt;/code&gt;) are not available inside function XML files. Using &lt;code&gt;tns:ElementName&lt;/code&gt; in a function's XPath will throw a &lt;code&gt;NullReferenceException&lt;/code&gt; at init time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Base64 response format.&lt;/strong&gt; &lt;code&gt;RunMapAsync&lt;/code&gt; does not return raw XML. The result is a &lt;code&gt;JToken&lt;/code&gt; with &lt;code&gt;$content&lt;/code&gt; containing base64-encoded output. You must decode it before asserting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond testing: the designer's save problem
&lt;/h2&gt;

&lt;p&gt;And even if Test Map worked, the visual designer has its own problems — changes that revert on reopen, string literals that lose quotes, expressions that get silently rewritten. I wrote a separate post about &lt;a href="https://dev.to/imdj/skip-the-designer-editing-logic-apps-data-mapper-lml-files-directly-40l0"&gt;editing LML files directly&lt;/a&gt; as a more reliable alternative to the designer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The XSLT Debugger extension is on the VS Code Marketplace for &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-darwin" rel="noopener noreferrer"&gt;macOS&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=danieljonathan.xsltdebugger-windows" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logicapps</category>
      <category>azure</category>
      <category>xslt</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Cut Logic Apps Standard Costs by 70% in Dev &amp; POC Azure Environments</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Sun, 15 Feb 2026 22:06:32 +0000</pubDate>
      <link>https://dev.to/imdj/cut-logic-apps-standard-costs-by-70-in-dev-poc-azure-environments-22on</link>
      <guid>https://dev.to/imdj/cut-logic-apps-standard-costs-by-70-in-dev-poc-azure-environments-22on</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Logic Apps Standard runs on an App Service Plan --- which means you pay 24/7.&lt;/p&gt;

&lt;p&gt;In dev and POC environments, that's unnecessary.&lt;/p&gt;

&lt;p&gt;Deploy when you need it.&lt;br&gt;
Tear down compute when you don't.&lt;br&gt;
Keep storage. Keep run history.&lt;/p&gt;

&lt;p&gt;Typical result: &lt;strong&gt;~70% cost reduction&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Core Idea
&lt;/h2&gt;

&lt;p&gt;Logic Apps Standard runs on an App Service Plan (WS1).&lt;/p&gt;

&lt;p&gt;That means compute is billed continuously --- even if no workflows are&lt;br&gt;
running.&lt;/p&gt;

&lt;p&gt;For non-production environments, that rarely makes sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy in the morning&lt;/li&gt;
&lt;li&gt;Work normally&lt;/li&gt;
&lt;li&gt;Tear down compute in the evening&lt;/li&gt;
&lt;li&gt;Keep the Storage Account intact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run history is preserved because it lives in storage --- not in the App Service Plan.&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.amazonaws.com%2Fuploads%2Farticles%2F4phorceqpy8vii0g2z06.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.amazonaws.com%2Fuploads%2Farticles%2F4phorceqpy8vii0g2z06.png" alt="Logic Apps Deploy/Teardown Architecture" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;A typical Azure Pricing Calculator estimate shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App Service Plan (WS1 × 730 hours): ~€152/month\&lt;/li&gt;
&lt;li&gt;Storage (example 1 TB, Hot LRS): ~€18/month\&lt;/li&gt;
&lt;li&gt;Total: ~€170/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That models a &lt;strong&gt;24/7 production setup&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But dev environments often run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 hours per day\&lt;/li&gt;
&lt;li&gt;~20 business days per month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's about &lt;strong&gt;160 hours&lt;/strong&gt;, not 730.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Insight
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Run history and workflow state are stored in the Storage Account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the Logic App&lt;/li&gt;
&lt;li&gt;Delete the App Service Plan&lt;/li&gt;
&lt;li&gt;Keep the Storage Account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All historical runs remain intact.&lt;/p&gt;

&lt;p&gt;When you redeploy, the Logic App reconnects to the same storage.&lt;/p&gt;


&lt;h2&gt;
  
  
  Cost Impact Example
&lt;/h2&gt;

&lt;p&gt;Instead of: €170/month (always-on)&lt;/p&gt;

&lt;p&gt;If you run: 160 hours/month&lt;/p&gt;

&lt;p&gt;Compute becomes approximately: €152 × (160 / 730) ≈ €33/month&lt;/p&gt;

&lt;p&gt;Add storage (~€18 example):&lt;/p&gt;

&lt;p&gt;≈ €51/month total. That's roughly &lt;strong&gt;70% savings&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠ Storage costs vary by actual GB used and transaction volume. &lt;br&gt;
The €18 example assumes 1 TB Hot LRS. Most dev environments use significantly less.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Daily Flow
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Morning
&lt;/h3&gt;

&lt;p&gt;Deploy infrastructure and workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./deploy.sh &amp;lt;rg&amp;gt; &amp;lt;region&amp;gt; &amp;lt;mode&amp;gt;
./deploy-workflows.sh &amp;lt;rg&amp;gt; &amp;lt;logicapp-name&amp;gt; &amp;lt;path&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What the deploy script does:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Switch to correct tenant/subscription&lt;/span&gt;
az login &lt;span class="nt"&gt;--tenant&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TENANT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
az account &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--subscription&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create resource group&lt;/span&gt;
az group create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Deploy Bicep template (creates App Service Plan, Logic App, Storage, App Insights)&lt;/span&gt;
az deployment group create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DEPLOYMENT_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-file&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMPLATE_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameters&lt;/span&gt; &lt;span class="s2"&gt;"@&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PARAM_FILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What the workflow deployment does:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Package workflows into ZIP&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORKFLOWS_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
zip &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMP_ZIP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"*.git*"&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"local.settings.json"&lt;/span&gt;

&lt;span class="c"&gt;# Deploy ZIP to Logic App&lt;/span&gt;
az functionapp deployment &lt;span class="nb"&gt;source &lt;/span&gt;config-zip &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOGIC_APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--src&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMP_ZIP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  During the Day
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run workflows&lt;/li&gt;
&lt;li&gt;Debug runs&lt;/li&gt;
&lt;li&gt;Inspect history&lt;/li&gt;
&lt;li&gt;Use monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No functional difference from always-on.&lt;/p&gt;




&lt;h3&gt;
  
  
  Evening
&lt;/h3&gt;

&lt;p&gt;Tear down compute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./teardown.sh &amp;lt;rg&amp;gt; &amp;lt;logicapp-name&amp;gt; &amp;lt;asp-name&amp;gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What the teardown script does:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Delete Logic App (keeps storage intact)&lt;/span&gt;
az webapp delete &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOGIC_APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Delete App Service Plan (the expensive part)&lt;/span&gt;
az appservice plan delete &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ASP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--yes&lt;/span&gt;

&lt;span class="c"&gt;# Storage Accounts are NOT deleted&lt;/span&gt;
&lt;span class="c"&gt;# Application Insights is NOT deleted&lt;/span&gt;
&lt;span class="c"&gt;# Run history remains in storage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2F3zoe0anp36v15euhcfi9.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.amazonaws.com%2Fuploads%2Farticles%2F3zoe0anp36v15euhcfi9.png" alt="Teardown Results" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Removes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logic App&lt;/li&gt;
&lt;li&gt;App Service Plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Run history&lt;/li&gt;
&lt;li&gt;Monitoring data&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When To Use This
&lt;/h2&gt;

&lt;p&gt;✅ Development environments&lt;br&gt;
✅ Test environments&lt;br&gt;
✅ POC and demo setups&lt;br&gt;
✅ Low-frequency scheduled workloads&lt;/p&gt;

&lt;p&gt;❌ Not for production&lt;/p&gt;

&lt;p&gt;Production workloads should remain always-on to meet SLA and reliability&lt;br&gt;
requirements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Important Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;App Service is billed per hour (partial hours count as full).&lt;/li&gt;
&lt;li&gt;Do not tear down while workflows are running.&lt;/li&gt;
&lt;li&gt;Recurrence triggers do not "catch up" when offline.&lt;/li&gt;
&lt;li&gt;Storage cost scales with run history retention and transaction
volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-decryptable secrets warning (AZFD0007):&lt;/strong&gt; During repeated teardown/redeploy cycles, you may see a diagnostic warning about "non-decryptable secrets backups." This is cosmetic and doesn't affect functionality. To clean it up, run:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  az rest &lt;span class="nt"&gt;--method&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--uri&lt;/span&gt; &lt;span class="s2"&gt;"https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{logicAppName}/syncfunctiontriggers?api-version=2022-03-01"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/errors-diagnostics/diagnostic-events/azfd0007" rel="noopener noreferrer"&gt;Microsoft AZFD0007 Docs&lt;/a&gt; for details.&lt;/p&gt;




&lt;h2&gt;
  
  
  Standard vs Consumption
&lt;/h2&gt;

&lt;p&gt;Why not just use Consumption?&lt;/p&gt;

&lt;p&gt;Consumption is pay-per-execution and ideal for low-volume workloads.&lt;/p&gt;

&lt;p&gt;Standard is preferred when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VNET integration&lt;/li&gt;
&lt;li&gt;Dedicated compute&lt;/li&gt;
&lt;li&gt;High throughput&lt;/li&gt;
&lt;li&gt;Advanced networking scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  This optimization applies specifically to &lt;strong&gt;Logic Apps Standard&lt;/strong&gt;.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;If your Logic App isn't needed 24/7, you shouldn't pay for it 24/7.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy when needed.&lt;/li&gt;
&lt;li&gt;Tear down compute when idle.&lt;/li&gt;
&lt;li&gt;Keep storage. Keep history.&lt;/li&gt;
&lt;li&gt;Same workflows.&lt;/li&gt;
&lt;li&gt;Same performance.&lt;/li&gt;
&lt;li&gt;Lower cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local Development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/imdj/building-logic-apps-locally-with-vs-code-http-blob-queue-more-1agj"&gt;Building Logic Apps Locally with VS Code (HTTP, Blob, Queue &amp;amp; More)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Azure Documentation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/pricing/details/logic-apps/" rel="noopener noreferrer"&gt;Azure Logic Apps Standard Pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/logic-apps/create-single-tenant-workflows-visual-studio-code" rel="noopener noreferrer"&gt;Set up local development for Logic Apps Standard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>logicapps</category>
      <category>azure</category>
      <category>infrastructure</category>
      <category>devops</category>
    </item>
    <item>
      <title>Inside Logic Apps Standard: Understanding Compute Units (CU) for Storage Scaling</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Wed, 14 Jan 2026 23:54:54 +0000</pubDate>
      <link>https://dev.to/imdj/inside-logic-apps-standard-understanding-compute-units-cu-for-storage-scaling-39k6</link>
      <guid>https://dev.to/imdj/inside-logic-apps-standard-understanding-compute-units-cu-for-storage-scaling-39k6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When your Logic App Standard workflows grow in volume and complexity, Azure provides a powerful horizontal scaling mechanism through &lt;strong&gt;Compute Units (CU)&lt;/strong&gt;. This post explains how CU architecture enables Logic Apps to distribute storage load across multiple storage accounts while maintaining data consistency and routing integrity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Storage Scaling Matters
&lt;/h2&gt;

&lt;p&gt;A single Azure Storage account has inherent limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;~2,000 requests per partition&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~20,000 requests per second&lt;/strong&gt; at account level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond these thresholds, request throttling occurs. For high-volume Logic Apps processing thousands of runs with dozens of actions each, a single storage account quickly becomes a bottleneck.&lt;/p&gt;

&lt;p&gt;Microsoft's guidance suggests &lt;strong&gt;approximately 100K action executions per minute per storage account&lt;/strong&gt; as a threshold for considering additional storage. Compute-heavy workflow actions may lower this threshold significantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Are Compute Units?
&lt;/h2&gt;

&lt;p&gt;Compute Units (also called &lt;strong&gt;Scale Units&lt;/strong&gt;) are Azure Logic Apps' approach to horizontal storage scaling. Rather than overloading a single storage account with all workflow execution data, Logic Apps can distribute the load across up to &lt;strong&gt;32 storage accounts&lt;/strong&gt; (CU00 through CU31).&lt;/p&gt;

&lt;p&gt;You configure this via &lt;code&gt;host.json&lt;/code&gt; using the &lt;code&gt;Runtime.ScaleUnitsCount&lt;/code&gt; parameter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Single Storage Architecture
&lt;/h2&gt;

&lt;p&gt;In the simplest deployment, all workflows use a single storage account:&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.amazonaws.com%2Fuploads%2Farticles%2Fcrqad0sk22x8j7eif4my.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.amazonaws.com%2Fuploads%2Farticles%2Fcrqad0sk22x8j7eif4my.png" alt=" " width="800" height="382"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logic App → CU00 (Master) → Single Storage Account
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With single storage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All workflow definitions, runs, and actions live in one storage account&lt;/li&gt;
&lt;li&gt;The storage account uses the &lt;strong&gt;CU00&lt;/strong&gt; suffix&lt;/li&gt;
&lt;li&gt;This works well for low-to-moderate workloads&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Scale-Out Storage Architecture
&lt;/h2&gt;

&lt;p&gt;When a Logic App needs to handle high-volume workflows, it can scale out to multiple storage accounts:&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.amazonaws.com%2Fuploads%2Farticles%2Fpo0ji2zag16nysjv2n9b.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.amazonaws.com%2Fuploads%2Farticles%2Fpo0ji2zag16nysjv2n9b.png" alt="Multi-Storage Architecture" width="800" height="340"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logic App → CU00 (Master)  → Storage Account 1
         → CU01            → Storage Account 2
         → CU02            → Storage Account 3
         → ...             → ...
         → CU31            → Storage Account 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each Compute Unit maps to its own dedicated storage account, allowing parallel writes and reads across multiple storage endpoints.&lt;/p&gt;




&lt;h2&gt;
  
  
  How CU Routing Works
&lt;/h2&gt;

&lt;p&gt;The key to this architecture is the &lt;strong&gt;Run ID&lt;/strong&gt;. Every workflow run includes a CU suffix that identifies which storage account contains its action data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;08584345852018133305811297792-CU00
08584345852018133305811297793-CU01
08584345852018133305711297794-CU02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fvo114lv3y1a71w9wod46.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.amazonaws.com%2Fuploads%2Farticles%2Fvo114lv3y1a71w9wod46.png" alt="CU Routing Storage Architecture" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extract CU suffix&lt;/strong&gt; from the RunId (e.g., &lt;code&gt;-CU01&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lookup storage account&lt;/strong&gt; mapping for the Logic App + CU combination&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query the correct storage account&lt;/strong&gt; for action data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ensures that when you request run details, the system knows exactly which storage account to query.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table Distribution Across CUs
&lt;/h2&gt;

&lt;p&gt;Not all tables are distributed the same way. Here's the key distinction:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table Type&lt;/th&gt;
&lt;th&gt;Location Determined By&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base Tables (&lt;code&gt;flows&lt;/code&gt;, &lt;code&gt;jobdefinitions&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;Always CU00 (Master)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow &lt;code&gt;flows&lt;/code&gt;, &lt;code&gt;runs&lt;/code&gt;, &lt;code&gt;histories&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ScaleUnit&lt;/code&gt; field in base flows metadata table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date-stamped &lt;code&gt;actions&lt;/code&gt; tables&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RunId-CUXX&lt;/code&gt; suffix (per-run routing)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Two Different Assignment Mechanisms
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Workflow Tables (ScaleUnit field)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you deploy a new workflow, Logic Apps assigns it to a CU. This assignment is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-deterministic from developer perspective&lt;/strong&gt; - you can't specify "put this workflow on CU02"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decided at deployment time&lt;/strong&gt; - when a new workflow is added, Logic Apps picks a CU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stored in the workflow's metadata&lt;/strong&gt; - the &lt;code&gt;ScaleUnit&lt;/code&gt; field in the flows table records the assignment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow's &lt;code&gt;flows&lt;/code&gt;, &lt;code&gt;runs&lt;/code&gt;, and &lt;code&gt;histories&lt;/code&gt; tables live in this assigned CU storage account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Action Tables (RunId-CUXX suffix)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the critical difference: &lt;strong&gt;action tables are distributed per-run, not per-workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each run execution gets assigned to a CU, indicated by the suffix in the RunId:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;08584345852018133305811297792CU01
08584345852115148130432249577CU00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single workflow's actions can be spread across &lt;strong&gt;multiple CU storage accounts&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;On any given date, you might have action tables for the same workflow in CU00, CU01, CU02, etc.&lt;/li&gt;
&lt;li&gt;Each storage account has its own date-stamped action table: &lt;code&gt;{WFIdentifier}20250114T000000Zactions&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example: Distributed Actions
&lt;/h3&gt;

&lt;p&gt;For a workflow with &lt;code&gt;ScaleUnit = CU00&lt;/code&gt;, runs might execute across different CUs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Workflow metadata (flows, runs, histories) → CU00 storage

Run 1: 08584345852018133305811297792-CU00 → actions in CU00 storage
Run 2: 08584345852018133305811297793-CU01 → actions in CU01 storage
Run 3: 08584345852018133305811297794-CU00 → actions in CU00 storage
Run 4: 08584345852018133305811297795-CU02 → actions in CU02 storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On January 14, 2025, this workflow could have action tables in three different storage accounts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CU00: flow{LAIdentifier}{WFIdentifier}20250114T000000Zactions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CU01: flow{LAIdentifier}{WFIdentifier}20250114T000000Zactions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CU02: flow{LAIdentifier}{WFIdentifier}20250114T000000Zactions&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What This Means
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App-level metadata stays centralized&lt;/strong&gt;: Base tables remain in CU00 (Master)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow metadata has a home&lt;/strong&gt;: &lt;code&gt;flows&lt;/code&gt;, &lt;code&gt;runs&lt;/code&gt;, &lt;code&gt;histories&lt;/code&gt; live in the workflow's assigned CU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actions are truly distributed&lt;/strong&gt;: Action data spreads across CUs based on run execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query complexity increases&lt;/strong&gt;: To get all actions for a date range, you may need to query multiple storage accounts&lt;/li&gt;
&lt;/ul&gt;




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

&lt;h3&gt;
  
  
  For High-Volume Workflows
&lt;/h3&gt;

&lt;p&gt;If your Logic App processes thousands of runs daily, each run might execute dozens of actions. Without CU distribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single storage account becomes a bottleneck&lt;/li&gt;
&lt;li&gt;Table partition limits could throttle performance&lt;/li&gt;
&lt;li&gt;Query latency increases as tables grow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With CU distribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write operations spread across multiple endpoints&lt;/li&gt;
&lt;li&gt;Each storage account handles a fraction of the load&lt;/li&gt;
&lt;li&gt;Queries route directly to the relevant storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  For Run History Queries
&lt;/h3&gt;

&lt;p&gt;When building tooling that queries run history (like export utilities or monitoring dashboards), you must account for CU routing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse the Run ID to extract the CU suffix&lt;/li&gt;
&lt;li&gt;Resolve the correct storage account for that CU&lt;/li&gt;
&lt;li&gt;Query the CU-specific storage for action data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ignoring CU routing means you'll miss action data entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuring Scale Units
&lt;/h2&gt;

&lt;p&gt;To enable multiple storage accounts, configure &lt;code&gt;host.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Runtime.ScaleUnitsCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Logic Apps to use 4 storage accounts (CU00-CU03). You'll need to provision the additional storage accounts and configure their connection strings or managed identity access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connection strings in app settings&lt;/li&gt;
&lt;li&gt;Managed identity (recommended for production)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Compute Units enable Logic Apps Standard to scale storage horizontally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CU00&lt;/strong&gt; is the master, holding app-level base tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow tables&lt;/strong&gt; (&lt;code&gt;flows&lt;/code&gt;, &lt;code&gt;runs&lt;/code&gt;, &lt;code&gt;histories&lt;/code&gt;) live in the CU specified by the &lt;code&gt;ScaleUnit&lt;/code&gt; field&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action tables&lt;/strong&gt; are distributed per-run based on the &lt;code&gt;RunId-CUXX&lt;/code&gt; suffix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same workflow, multiple CUs&lt;/strong&gt;: A workflow's actions can span multiple storage accounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure via&lt;/strong&gt; &lt;code&gt;Runtime.ScaleUnitsCount&lt;/code&gt; in &lt;code&gt;host.json&lt;/code&gt; (max 32)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threshold guidance&lt;/strong&gt;: ~100K action executions per minute per storage account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding this architecture is essential when building tooling around Logic Apps, querying run histories programmatically, or troubleshooting high-volume workflow performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techcommunity.microsoft.com/blog/integrationsonazureblog/scaling-logic-app-standard-for-high-throughput-scenarios/3866731" rel="noopener noreferrer"&gt;Scaling Logic App Standard for high-throughput scenarios&lt;/a&gt; - Microsoft Tech Community&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This is part of the "Inside Logic Apps Standard" series, exploring the internals of Azure Logic Apps Standard architecture.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logicapps</category>
      <category>azure</category>
      <category>microsoft</category>
      <category>azureintegrationservices</category>
    </item>
    <item>
      <title>Inside Logic Apps Standard: How Azure Tables Store Your Workflows</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Wed, 14 Jan 2026 23:28:43 +0000</pubDate>
      <link>https://dev.to/imdj/inside-logic-apps-standard-how-azure-tables-store-your-workflows-5125</link>
      <guid>https://dev.to/imdj/inside-logic-apps-standard-how-azure-tables-store-your-workflows-5125</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Azure Logic Apps Standard uses Azure Table Storage as its primary persistence layer for workflow state, execution history, and runtime metadata. This document provides a comprehensive reference to the table architecture, naming conventions, and schema patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table Categories
&lt;/h2&gt;

&lt;p&gt;Logic Apps Standard organizes data into two distinct table categories:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Base Tables&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logic App Resource&lt;/td&gt;
&lt;td&gt;Shared runtime metadata across all workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flow Tables&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Individual Workflow&lt;/td&gt;
&lt;td&gt;Isolated execution data per workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This separation enables &lt;strong&gt;workflow isolation&lt;/strong&gt;, &lt;strong&gt;horizontal scaling&lt;/strong&gt;, and &lt;strong&gt;independent lifecycle management&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Base Tables (App-Level)
&lt;/h2&gt;

&lt;p&gt;Base Tables store metadata that applies to the entire Logic App resource. All workflows within a Logic App share these tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Table Naming Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flow{LAIdentifier}{suffix}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LAIdentifier&lt;/code&gt; = First 15 characters of MurmurHash64(LogicAppName)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;suffix&lt;/code&gt; = Table type identifier&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Base Table Types
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table Suffix&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Key Data&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Master workflow registry&lt;/td&gt;
&lt;td&gt;Workflow definitions, state, CU assignment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flowruntimecontext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runtime execution context&lt;/td&gt;
&lt;td&gt;Flow state, execution cluster type, runtime config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flowaccesskeys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow access keys&lt;/td&gt;
&lt;td&gt;Primary/secondary keys for workflow API access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flowsubscriptions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subscription registration&lt;/td&gt;
&lt;td&gt;Tenant registration, subscription state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flowsubscriptionsummary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow counts &amp;amp; summaries&lt;/td&gt;
&lt;td&gt;Flow counts by state (enabled/disabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jobdefinitions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Job scheduler metadata&lt;/td&gt;
&lt;td&gt;Execution state, retry config, repeat schedules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;For a Logic App named &lt;code&gt;OrderProcessing&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LAIdentifier = flow7365adc025615f3

Base Tables:
├── flow7365adc025615f3flows
├── flow7365adc025615f3flowruntimecontext
├── flow7365adc025615f3flowaccesskeys
├── flow7365adc025615f3flowsubscriptions
└── flow7365adc025615f3jobdefinitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Flow Tables (Workflow-Level)
&lt;/h2&gt;

&lt;p&gt;Flow Tables store execution data specific to each workflow. Each workflow gets its own isolated set of tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Table Naming Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flow{LAIdentifier}{WFIdentifier}{suffix}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LAIdentifier&lt;/code&gt; = First 15 characters of MurmurHash64(LogicAppName)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WFIdentifier&lt;/code&gt; = First 15 characters of MurmurHash64(WorkflowId)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;suffix&lt;/code&gt; = Table type identifier&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flow Table Types
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table Suffix&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Key Data&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow definition cache&lt;/td&gt;
&lt;td&gt;Latest definition, parameters, runtime context, CU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;runs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run metadata&lt;/td&gt;
&lt;td&gt;Run status, timing, trigger info, correlation IDs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;histories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Trigger history&lt;/td&gt;
&lt;td&gt;Trigger fires, evaluation results, input/output links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Action executions&lt;/td&gt;
&lt;td&gt;Action status, timing, input/output blob links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;variables&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow variables&lt;/td&gt;
&lt;td&gt;Variable name, data type, value links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;concurrentruns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Concurrency tracking&lt;/td&gt;
&lt;td&gt;Active run coordination (when concurrency is enabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Date-Stamped Tables
&lt;/h3&gt;

&lt;p&gt;Action-related tables use date partitioning for scalability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{WFIdentifier}yyyyMMddT000000Z{suffix}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: &lt;code&gt;1c859134ef297b720250114T000000Zactions&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;For workflow &lt;code&gt;ProcessOrder&lt;/code&gt; in &lt;code&gt;OrderProcessing&lt;/code&gt; Logic App:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LAIdentifier = flow7365adc025615f3
WFIdentifier = 1c859134ef297b7

Flow Tables:
├── flow7365adc025615f31c859134ef297b7flows
├── flow7365adc025615f31c859134ef297b7runs
├── flow7365adc025615f31c859134ef297b7histories
├── flow7365adc025615f31c859134ef297b720250114T000000Zactions
└── flow7365adc025615f31c859134ef297b720250114T000000Zvariables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Table Key Fields
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Base Flows Table (Master Registry)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlowId&lt;/td&gt;
&lt;td&gt;Unique workflow identifier (GUID)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowName&lt;/td&gt;
&lt;td&gt;Workflow name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kind&lt;/td&gt;
&lt;td&gt;Workflow type (&lt;code&gt;Stateful&lt;/code&gt; / &lt;code&gt;Stateless&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Enabled&lt;/code&gt; or &lt;code&gt;Disabled&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScaleUnit&lt;/td&gt;
&lt;td&gt;CU assignment (&lt;code&gt;CU00&lt;/code&gt;, &lt;code&gt;CU01&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DefinitionCompressed&lt;/td&gt;
&lt;td&gt;Compressed workflow JSON definition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ParametersCompressed&lt;/td&gt;
&lt;td&gt;Compressed workflow parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RuntimeConfiguration&lt;/td&gt;
&lt;td&gt;Retention settings, operation options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CreatedTime / ChangedTime&lt;/td&gt;
&lt;td&gt;Lifecycle timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  FlowRuntimeContext Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlowId&lt;/td&gt;
&lt;td&gt;Workflow identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowState&lt;/td&gt;
&lt;td&gt;Current workflow state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowExecutionClusterType&lt;/td&gt;
&lt;td&gt;Execution cluster (&lt;code&gt;Classic&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kind&lt;/td&gt;
&lt;td&gt;Workflow type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RuntimeConfiguration&lt;/td&gt;
&lt;td&gt;Runtime settings JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowSequenceId&lt;/td&gt;
&lt;td&gt;Sequence identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  FlowAccessKeys Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlowId&lt;/td&gt;
&lt;td&gt;Workflow identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowAccessKeyName&lt;/td&gt;
&lt;td&gt;Key name (e.g., &lt;code&gt;default&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PrimaryKey&lt;/td&gt;
&lt;td&gt;Primary access key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SecondaryKey&lt;/td&gt;
&lt;td&gt;Secondary access key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  FlowSubscriptions Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SubscriptionId&lt;/td&gt;
&lt;td&gt;Azure subscription identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Registration state (&lt;code&gt;Registered&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Properties&lt;/td&gt;
&lt;td&gt;Tenant ID and registered features JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RegistrationDate&lt;/td&gt;
&lt;td&gt;Subscription registration timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  FlowSubscriptionSummary Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlowCountsByState&lt;/td&gt;
&lt;td&gt;JSON with enabled/disabled workflow counts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowId / FlowName&lt;/td&gt;
&lt;td&gt;Workflow reference (for per-workflow entries)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Workflow state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScaleUnit&lt;/td&gt;
&lt;td&gt;CU assignment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DefinitionCompressed&lt;/td&gt;
&lt;td&gt;Compressed definition (cached)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  JobDefinitions Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JobId&lt;/td&gt;
&lt;td&gt;Unique job identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JobPartition&lt;/td&gt;
&lt;td&gt;Job partition key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Callback&lt;/td&gt;
&lt;td&gt;Job callback type (e.g., &lt;code&gt;LinearSequencerJob&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Job state (&lt;code&gt;Completed&lt;/code&gt;, &lt;code&gt;Waiting&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ExecutionState&lt;/td&gt;
&lt;td&gt;Current execution state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;StartTime / EndTime&lt;/td&gt;
&lt;td&gt;Job scheduling window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NextExecutionTime&lt;/td&gt;
&lt;td&gt;Scheduled next execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RepeatMode / RepeatUnit / RepeatInterval&lt;/td&gt;
&lt;td&gt;Recurrence configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RetryMode / RetryCount / RetryInterval&lt;/td&gt;
&lt;td&gt;Retry configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CurrentExecutionCount&lt;/td&gt;
&lt;td&gt;Total executions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LastExecutionStatus / LastExecutionTime&lt;/td&gt;
&lt;td&gt;Most recent execution info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BinaryMetadata00-15&lt;/td&gt;
&lt;td&gt;Compressed job metadata chunks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Runs Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlowId / FlowName&lt;/td&gt;
&lt;td&gt;Workflow reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Succeeded&lt;/code&gt;, &lt;code&gt;Failed&lt;/code&gt;, &lt;code&gt;Running&lt;/code&gt;, &lt;code&gt;Cancelled&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CreatedTime / EndTime&lt;/td&gt;
&lt;td&gt;Run timing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TriggerName&lt;/td&gt;
&lt;td&gt;Trigger that initiated the run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CorrelationId&lt;/td&gt;
&lt;td&gt;Request correlation tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ClientTrackingId&lt;/td&gt;
&lt;td&gt;Client-provided tracking ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TriggerCompressed&lt;/td&gt;
&lt;td&gt;Compressed trigger payload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OutputsCompressed&lt;/td&gt;
&lt;td&gt;Compressed run outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Histories Table (Trigger History)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HistoryName&lt;/td&gt;
&lt;td&gt;History entry identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TriggerName&lt;/td&gt;
&lt;td&gt;Trigger reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fired&lt;/td&gt;
&lt;td&gt;Boolean - whether trigger actually fired&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;Trigger evaluation result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InputsLinkCompressed&lt;/td&gt;
&lt;td&gt;Link to trigger inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OutputsLinkCompressed&lt;/td&gt;
&lt;td&gt;Link to trigger outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TrackingId&lt;/td&gt;
&lt;td&gt;Correlation tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Actions Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ActionName&lt;/td&gt;
&lt;td&gt;Action identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ActionRepetitionName&lt;/td&gt;
&lt;td&gt;Loop iteration index (e.g., &lt;code&gt;000000&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Succeeded&lt;/code&gt;, &lt;code&gt;Skipped&lt;/code&gt;, &lt;code&gt;Failed&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;td&gt;Result code (&lt;code&gt;OK&lt;/code&gt;, &lt;code&gt;ActionSkipped&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CreatedTime / CompletedTime&lt;/td&gt;
&lt;td&gt;Action timing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InputsLinkCompressed&lt;/td&gt;
&lt;td&gt;Link to action inputs blob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OutputsLinkCompressed&lt;/td&gt;
&lt;td&gt;Link to action outputs blob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RepeatItemIndex&lt;/td&gt;
&lt;td&gt;Loop iteration number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RepeatItemScopeName&lt;/td&gt;
&lt;td&gt;Parent loop action name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;Error details (if failed)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Variables Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VariableName&lt;/td&gt;
&lt;td&gt;Variable identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataType&lt;/td&gt;
&lt;td&gt;Variable type (&lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Boolean&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowId / FlowName&lt;/td&gt;
&lt;td&gt;Workflow reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowRunSequenceId&lt;/td&gt;
&lt;td&gt;Run sequence identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OperationId&lt;/td&gt;
&lt;td&gt;Operation that modified the variable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OperationSequenceId&lt;/td&gt;
&lt;td&gt;Operation sequence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InputsLinkCompressed&lt;/td&gt;
&lt;td&gt;Link to variable input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ValueLinkCompressed&lt;/td&gt;
&lt;td&gt;Link to current variable value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CreatedTime / ChangedTime&lt;/td&gt;
&lt;td&gt;Variable lifecycle timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Workflow Flows Table (Per-Workflow Cache)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlowId / FlowName&lt;/td&gt;
&lt;td&gt;Workflow identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kind&lt;/td&gt;
&lt;td&gt;Workflow type (&lt;code&gt;Stateful&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Enabled&lt;/code&gt; or &lt;code&gt;Disabled&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScaleUnit&lt;/td&gt;
&lt;td&gt;CU assignment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DefinitionCompressed&lt;/td&gt;
&lt;td&gt;Compressed latest workflow definition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ParametersCompressed&lt;/td&gt;
&lt;td&gt;Compressed parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RuntimeConfiguration&lt;/td&gt;
&lt;td&gt;Runtime settings JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RuntimeContext&lt;/td&gt;
&lt;td&gt;Compressed runtime context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowSequenceId&lt;/td&gt;
&lt;td&gt;Definition sequence ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlowUpdatedTime&lt;/td&gt;
&lt;td&gt;Last definition update time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Identifier Generation
&lt;/h2&gt;

&lt;p&gt;Logic Apps uses MurmurHash64 for deterministic, collision-resistant identifier generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Algorithm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate Logic App identifier (LAIdentifier)&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;laIdentifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"flow"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;MurmurHash64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logicAppName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Generate Workflow identifier (WFIdentifier)&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;wfIdentifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;MurmurHash64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workflowId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Complete table name&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;laIdentifier&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;wfIdentifier&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Constraints
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Table names: Max 63 characters (Azure Table Storage limit)&lt;/li&gt;
&lt;li&gt;Characters: Lowercase alphanumeric only&lt;/li&gt;
&lt;li&gt;Hash truncation: 15 characters provides sufficient uniqueness&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Table Name Format
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base:  flow{LAIdentifier}{suffix}
Flow:  flow{LAIdentifier}{WFIdentifier}{suffix}
Dated: flow{LAIdentifier}{WFIdentifier}{yyyyMMddT000000Z}{suffix}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Suffixes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Suffix&lt;/th&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;flows&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;Definitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;runs&lt;/td&gt;
&lt;td&gt;Flow&lt;/td&gt;
&lt;td&gt;Run history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;actions&lt;/td&gt;
&lt;td&gt;Flow&lt;/td&gt;
&lt;td&gt;Action executions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowsubscriptions&lt;/td&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;Trigger subscriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jobdefinitions&lt;/td&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;Scheduled jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Update Reference:&lt;/p&gt;

&lt;p&gt;Flow Tables in SQL for Hybrid Deployment&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.amazonaws.com%2Fuploads%2Farticles%2Fizeqwz5e6kvnmqhhxusw.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.amazonaws.com%2Fuploads%2Farticles%2Fizeqwz5e6kvnmqhhxusw.png" alt=" " width="800" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>logicapps</category>
      <category>storageaccount</category>
      <category>azure</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Testing Azure Logic Apps: Implementation Guide</title>
      <dc:creator>Daniel Jonathan</dc:creator>
      <pubDate>Thu, 18 Dec 2025 16:04:40 +0000</pubDate>
      <link>https://dev.to/imdj/testing-azure-logic-apps-part-2-production-implementation-guide-1n3m</link>
      <guid>https://dev.to/imdj/testing-azure-logic-apps-part-2-production-implementation-guide-1n3m</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;In Part 4 , we learned LogicAppUnit fundamentals. Now we build real test suites using &lt;strong&gt;production workflows&lt;/strong&gt; that mix HTTP actions and ServiceProvider connectors. You'll see project structure, mock data management, happy/sad path testing, and debugging techniques - all from working production code.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;✅ Structure testable Logic App projects&lt;br&gt;
✅ Mock ServiceProvider connectors (Blob, SAS URLs)&lt;br&gt;
✅ Test happy paths and error handling&lt;br&gt;
✅ Manage mock data with JSON files&lt;br&gt;
✅ Debug test failures efficiently&lt;/p&gt;




&lt;h2&gt;
  
  
  The Workflows
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔷 Product Creation (Complex)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Flow&lt;/strong&gt;: Validate → Create Product → Upload Blob → Get SAS URL → Update Metadata → Respond&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests&lt;/strong&gt;: Error handling, validation, request inspection&lt;/p&gt;

&lt;h3&gt;
  
  
  🔷 Blob Search (Simple)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Flow&lt;/strong&gt;: Build Request → Call Function → Check Result → Respond&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests&lt;/strong&gt;: Success, empty results, API errors&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AzMCP/
├── src/
│   ├── LABasicDemo/              # Logic App
│   │   ├── wf-product-create/    # HTTP action workflow
│   │   │   └── workflow.json
│   │   ├── wf-builtin-connector/ # Built-in connector (Azure Blob)
│   │   │   └── workflow.json
│   │   ├── wf-mngconnector-pub/  # Managed connector(Service Bus, O365)
│   │   │   └── workflow.json
│   │   ├── wf-mngconnector-sub/       
│   │   │   └── workflow.json
│   │   ├── parameters.json             # @appsetting() refs
│   │   ├── connections.json            # Production connections
│   │   └── local.settings.json
│   │
│   └── CommonComps.UnitTests/          # Tests
│       ├── LogicApp/
│       │   ├── wf-product-create/
│       │   │   ├── WfProductCreateTests.cs
│       │   │   └── MockData/
│       │   │       ├── CreateProductResponse.json
│       │   │       └── UploadBlobResponse.json
│       │   ├── wf-builtin-connector/
│       │   │   ├── WfBuiltInConnectorTests.cs
│       │   │   └── MockData/
│       │   │       ├── UploadBlobResponse.json
│       │   │       └── GetBlobSASUriResponse.json
│       │   ├── wf-mngconnector-pub/
│       │   │   ├── WfMngConnectorPubTests.cs
│       │   │   └── MockData/
│       │   │       ├── Office365SendEmailResponse.json
│       │   │       └── ServiceBusSendMessageResponse.json
│       │   ├── wf-mngconnector-sub/
│       │   │   ├── WfMngConnectorSubTests.cs
│       │   │   └── MockData/
│       │   │       └── ServiceBusTriggerPayload.json
│       │   └── Helpers/
│       │       └── TestConfigHelper.cs
│       └── testConfiguration.json
│       └── parameters.unittest.json
│       └── connection.unittest.json
│       └── local.setting.unittest.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Configuration Files
&lt;/h2&gt;

&lt;h3&gt;
  
  
  parameters.json (Production)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apiUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('apiUrl')"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servicebus-ConnectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('servicebus-ConnectionRuntimeUrl')"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servicebus-Authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ManagedServiceIdentity"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"office365-ConnectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('office365-ConnectionRuntimeUrl')"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"office365-Authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ManagedServiceIdentity"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  parameters.unittest.json (Test Override)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apiUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('apiUrl')"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servicebus-ConnectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('servicebus-ConnectionRuntimeUrl')"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servicebus-Authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Raw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parameter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('servicebus-connectionKey')"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"office365-ConnectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('office365-ConnectionRuntimeUrl')"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"office365-Authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Raw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parameter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('office365-connectionKey')"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  local.settings.json
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"IsEncrypted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AzureWebJobsStorage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UseDevelopmentStorage=true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"FUNCTIONS_WORKER_RUNTIME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AzureWebJobsFeatureFlags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EnableWorkerIndexing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"apiUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-api.azurewebsites.net/api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AzureBlob_connectionString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DefaultEndpointsProtocol=https;AccountName=youraccount;AccountKey=dummy;EndpointSuffix=core.windows.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"servicebus-connectionKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dummy-key-for-tests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"servicebus-ConnectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://dummy-servicebus.servicebus.windows.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"office365-connectionKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dummy-key-for-tests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"office365-ConnectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://dummy-office365.office365.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"WORKFLOWS_SUBSCRIPTION_ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00000000-0000-0000-0000-000000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"WORKFLOWS_LOCATION_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eastus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"WORKFLOWS_RESOURCE_GROUP_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rg-test"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  connections.json (Production)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"serviceProviderConnections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AzureBlob"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parameterValues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"connectionString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('AzureBlob_connectionString')"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parameterSetName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connectionString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"serviceProvider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/serviceProviders/AzureBlob"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"managedApiConnections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"servicebus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/@{appsetting('WORKFLOWS_SUBSCRIPTION_ID')}/providers/Microsoft.Web/locations/@{appsetting('WORKFLOWS_LOCATION_NAME')}/managedApis/servicebus"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/@{appsetting('WORKFLOWS_SUBSCRIPTION_ID')}/resourceGroups/@{appsetting('WORKFLOWS_RESOURCE_GROUP_NAME')}/providers/Microsoft.Web/connections/servicebus"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@parameters('servicebus-ConnectionRuntimeUrl')"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@parameters('servicebus-Authentication')"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"office365"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/@{appsetting('WORKFLOWS_SUBSCRIPTION_ID')}/providers/Microsoft.Web/locations/@{appsetting('WORKFLOWS_LOCATION_NAME')}/managedApis/office365"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/@{appsetting('WORKFLOWS_SUBSCRIPTION_ID')}/resourceGroups/@{appsetting('WORKFLOWS_RESOURCE_GROUP_NAME')}/providers/Microsoft.Web/connections/office365"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@parameters('office365-ConnectionRuntimeUrl')"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@parameters('office365-Authentication')"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  connections.unittest.json (Test Override)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"serviceProviderConnections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AzureBlob"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parameterValues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"connectionString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('AzureBlob_connectionString')"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parameterSetName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connectionString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"serviceProvider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/serviceProviders/AzureBlob"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"managedApiConnections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"servicebus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/@{appsetting('WORKFLOWS_SUBSCRIPTION_ID')}/providers/Microsoft.Web/locations/@{appsetting('WORKFLOWS_LOCATION_NAME')}/managedApis/servicebus"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/@{appsetting('WORKFLOWS_SUBSCRIPTION_ID')}/resourceGroups/@{appsetting('WORKFLOWS_RESOURCE_GROUP_NAME')}/providers/Microsoft.Web/connections/servicebus"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Raw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"parameter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('servicebus-connectionKey')"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@parameters('servicebus-ConnectionRuntimeUrl')"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"office365"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/@{appsetting('WORKFLOWS_SUBSCRIPTION_ID')}/providers/Microsoft.Web/locations/@{appsetting('WORKFLOWS_LOCATION_NAME')}/managedApis/office365"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/@{appsetting('WORKFLOWS_SUBSCRIPTION_ID')}/resourceGroups/@{appsetting('WORKFLOWS_RESOURCE_GROUP_NAME')}/providers/Microsoft.Web/connections/office365"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Raw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"parameter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@appsetting('office365-connectionKey')"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connectionRuntimeUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@parameters('office365-ConnectionRuntimeUrl')"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  testConfiguration.json
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"azurite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enableAzuritePortCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"writeMockRequestMatchingLogs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"externalApiUrlsToMock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"https://your-api.azurewebsites.net"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"builtInConnectorsToMock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"uploadBlob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"getBlobSASUri"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Configuration Points
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Production vs Test&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;parameters.json&lt;/code&gt; uses &lt;code&gt;ManagedServiceIdentity&lt;/code&gt; → &lt;code&gt;parameters.unittest.json&lt;/code&gt; uses &lt;code&gt;Raw&lt;/code&gt; auth&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;connections.json&lt;/code&gt; references &lt;code&gt;@parameters()&lt;/code&gt; → &lt;code&gt;connections.unittest.json&lt;/code&gt; has explicit &lt;code&gt;Raw&lt;/code&gt; auth&lt;/li&gt;
&lt;li&gt;Test files are swapped in by &lt;code&gt;TestConfigHelper&lt;/code&gt; before &lt;code&gt;Initialize()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Critical Settings&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All parameters use &lt;code&gt;@appsetting()&lt;/code&gt; - never hardcode values&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;externalApiUrlsToMock&lt;/code&gt; contains base URLs only (no paths)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;builtInConnectorsToMock&lt;/code&gt; lists operationIds from workflows&lt;/li&gt;
&lt;li&gt;Managed connector auth MUST be &lt;code&gt;Raw&lt;/code&gt; type for tests&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Test Base Class
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WfProductCreateTests&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WorkflowTestBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;LogicAppTestConfigHelper&lt;/span&gt; &lt;span class="n"&gt;_configHelper&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;MockDataPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;AppDomain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentDomain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"../../../LogicApp/wf-product-create/MockData"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;WfProductCreateTests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_configHelper&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LogicAppTestConfigHelper&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_configHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ApplyTestConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"../LABasicDemo"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"../../../../LABasicDemo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"wf-product-create"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_configHelper&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;RestoreAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;JObject&lt;/span&gt; &lt;span class="nf"&gt;LoadMockData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;JObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MockDataPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example 1: Happy Path Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CreateProduct_HappyPath_Success&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITestRunner&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateTestRunner&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 📂 Load mocks&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;productResp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;LoadMockData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CreateProductResponse.json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;blobResp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;LoadMockData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UploadBlobResponse.json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sasResp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;LoadMockData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetSasUrlResponse.json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 🌐 HTTP action - CreateProduct (INTERCEPTED)&lt;/span&gt;
        &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MockRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsingPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathMatchType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/Products"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RespondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MockResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithSuccess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithContentAsJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productResp&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// 📦 Built-in - uploadblob (CONVERTED)&lt;/span&gt;
        &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MockRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsingPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathMatchType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/uploadblob"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uploadblob"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RespondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MockResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithSuccess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithContentAsJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blobResp&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// 🔗 Built-in - GetSasUrl (CONVERTED)&lt;/span&gt;
        &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MockRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsingPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathMatchType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/GetSasUrl"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetSasUrl"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RespondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MockResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithSuccess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithContentAsJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sasResp&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// 🏷️ HTTP action - updatemetadata (INTERCEPTED)&lt;/span&gt;
        &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MockRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsingPut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathMatchType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"uploadproductinfo"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RespondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MockResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;WithSuccess&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// 🚀 Trigger&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TriggerWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"{
                ""name"": ""LaptopM4"",
                ""sku"": ""006"",
                ""categoryId"": 1,
                ""price"": 1500
            }"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// ✅ Assert&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WorkflowRunStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Succeeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkflowRunStatus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Succeeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWorkflowActionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uploadblob"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Mock Data Files
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CreateProductResponse.json (HTTP)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LaptopM4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"006"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  UploadBlobResponse.json (ServiceProvider - Raw!)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blobUri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:7075/uploadproductinfo/LaptopM4006.json"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ NO &lt;code&gt;statusCode&lt;/code&gt;/&lt;code&gt;body&lt;/code&gt; wrapper&lt;/li&gt;
&lt;li&gt;✅ Points to &lt;code&gt;localhost:7075&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example 2: Validation Error
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CreateProduct_WithoutCategoryId_Returns400&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITestRunner&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateTestRunner&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ❌ No mocks - fails before API calls&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TriggerWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"{ ""name"": ""Test"" }"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// ✅ Assert&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkflowWasTerminated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Skipped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWorkflowActionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CreateProduct"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example 3: API Error Handling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CreateProduct_WhenApiFails_Returns500&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITestRunner&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateTestRunner&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ❌ Mock API failure&lt;/span&gt;
        &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MockRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsingPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathMatchType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/Products"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RespondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MockResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithInternalServerError&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TriggerWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"{
                ""name"": ""Test"",
                ""categoryId"": 1
            }"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// ✅ Assert error handling&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkflowWasTerminated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InternalServerError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWorkflowActionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TryProcessProduct"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example 4: Request Inspection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CreateProduct_SetsCorrectMetadata&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITestRunner&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateTestRunner&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... setup all mocks ...&lt;/span&gt;

        &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TriggerWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* payload */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 🔍 Inspect actual request&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mockRequests&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MockRequests&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;metadataReq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mockRequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Put&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uploadproductinfo"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// ✅ Verify header&lt;/span&gt;
        &lt;span class="n"&gt;metadataReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;NotBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;metadataReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ContainKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-ms-meta-productdoc"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;metadataReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"x-ms-meta-productdoc"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LaptopM4006.json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example 5: Simple HTTP Workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;BlobSearch_ReturnsResults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITestRunner&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateTestRunner&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;searchResp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;LoadMockData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BlobSearchResponse.json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MockRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsingPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathMatchType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/BlobMetadataSearch"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RespondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MockResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithSuccess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithContentAsJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TriggerWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"{
                ""container"": ""products"",
                ""metadataKey"": ""productdoc"",
                ""metadataValue"": ""LaptopM4""
            }"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WorkflowRunStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Succeeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkflowRunStatus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing Strategy Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Verifies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Happy Path&lt;/td&gt;
&lt;td&gt;All actions succeed&lt;/td&gt;
&lt;td&gt;Status, actions, response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;Missing field&lt;/td&gt;
&lt;td&gt;Terminate, 400, skipped actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Error&lt;/td&gt;
&lt;td&gt;External fail&lt;/td&gt;
&lt;td&gt;Error handling, 500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty Results&lt;/td&gt;
&lt;td&gt;No data&lt;/td&gt;
&lt;td&gt;Success, count=0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request&lt;/td&gt;
&lt;td&gt;Payload check&lt;/td&gt;
&lt;td&gt;Headers, content correct&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Running Tests
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start Azurite&lt;/span&gt;
npx azurite &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; ./.azurite

&lt;span class="c"&gt;# Run tests&lt;/span&gt;
dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"FullyQualifiedName~LogicApp"&lt;/span&gt;

&lt;span class="c"&gt;# Single test with logging&lt;/span&gt;
&lt;span class="nv"&gt;LOGICAPPUNIT_TESTLOGGER_VERBOSITY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Trace &lt;span class="se"&gt;\&lt;/span&gt;
  dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"FullyQualifiedName~LogicApps"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected Output&lt;/strong&gt;:&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.amazonaws.com%2Fuploads%2Farticles%2Ffl06vblnrfbb31upg87t.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.amazonaws.com%2Fuploads%2Farticles%2Ffl06vblnrfbb31upg87t.png" alt=" " width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging Quick Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🐛 Mock Requests Count = 0
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"@appsetting"&lt;/span&gt; parameters.json
&lt;span class="nb"&gt;cat &lt;/span&gt;testConfiguration.json | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"externalApiUrlsToMock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🐛 Built-In Connector Null
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;❌&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;WRONG&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"blobUri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;✅&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;CORRECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"blobUri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:7075/..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🐛 Action Skipped
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Find which action failed first&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"CreateProduct: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWorkflowActionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CreateProduct"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"uploadblob: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;testRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWorkflowActionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uploadblob"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Structure
&lt;/h3&gt;

&lt;p&gt;✅ One test class per workflow&lt;br&gt;
✅ MockData in JSON files&lt;br&gt;
✅ Helper for config management&lt;/p&gt;

&lt;h3&gt;
  
  
  Patterns
&lt;/h3&gt;

&lt;p&gt;✅ HTTP actions → endpoint path&lt;br&gt;
✅ Built-in connectors → action name + &lt;code&gt;.FromAction()&lt;/code&gt;&lt;br&gt;
✅ Dynamic URLs → &lt;code&gt;PathMatchType.Contains&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;✅ Test happy AND sad paths&lt;br&gt;
✅ Verify workflow + action statuses&lt;br&gt;
✅ Inspect requests when needed&lt;br&gt;
✅ Keep mock data realistic&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;p&gt;✅ Check &lt;code&gt;MockRequests&lt;/code&gt; count first&lt;br&gt;
✅ Enable &lt;code&gt;writeMockRequestMatchingLogs&lt;/code&gt;&lt;br&gt;
✅ Verify response formats&lt;br&gt;
✅ Use focused test filters&lt;/p&gt;

</description>
      <category>logicapps</category>
      <category>unittest</category>
      <category>azure</category>
      <category>microsoft</category>
    </item>
  </channel>
</rss>
