Weekly Review: Business Central AL Development – March 8–14, 2026

Highlighting posts and resources from the Business Central development community — March 8–14, 2026

This week’s roundup covers nine posts spanning BC28 preview features, AI-powered development workflows, and practical AL tooling. BC28’s new semantic search codeunit landed alongside posts on designing agents for production, automating telemetry triage with Azure Logic Apps, and connecting Business Central to Copilot Studio via the built-in MCP Server Configuration. We also saw the AL Development Tools ship a standalone MCP server for coding agents, new DateFormula-based posting windows that eliminate manual monthly date updates, a practical guide to GitHub Copilot’s modes in AL projects, and a case for treating context engineering as the real lever for AI-assisted BC development.


Recent Posts (March 8–14, 2026)

➡️ 1. Using the Standalone MCP Server Provided as Part of the AL Development Tools

📇 Author: Stefano Demiliani
🌎 Link: demiliani.com
📝 Summary: Stefano walks through the launchmcpserver command added in v17.x of the AL Development Tools .NET package — al launchmcpserver <projectFolder> starts an HTTP MCP server (default port 5000) that exposes al_build, al_debug, al_publish, al_setbreakpoint, al_snapshotdebugging, and al_symbolsearch as tools any AI coding agent can invoke. The post covers installation via dotnet tool install, optional parameters (--port, --transport, --ruleset), and live examples of setting breakpoints and building projects from an agent chat.


➡️ 2. I Built an AI That Turns Business Central Telemetry into GitHub Issues (Automatically)

📇 Author: Silviu Virlan
🌎 Link: svirlan.com
📝 Summary: Silviu builds an end-to-end pipeline — an AL extension that deliberately triggers RT0012 (database lock timeout), a daily Azure Logic App that queries Application Insights via KQL, an AI model that reads the offending .al source code, and automatic GitHub Issue creation with root cause analysis, an actionable checklist, and prevention tips. The whole stack deploys with a single az deployment group create via Bicep, and expanding to all RT* performance signals is one KQL filter change.


➡️ 3. BC28: AL Developers Can Use Semantic Search on Data and Metadata

📇 Author: Yun Zhu
🗓️ Date: March 11, 2026
🌎 Link: yzhums.com
📝 Summary: Yun Zhu covers Codeunit 2000000025 “Semantic Search” arriving in BC28 with methods SetMaxResults, SetSearchTarget(RecordRef), and FindSimilarByField() for running vector-style similarity searches against Business Central data and metadata from AL code. The catch: the codeunit is currently Scope = 'OnPrem', so it can’t be called from cloud extensions yet.


➡️ 4. Where GitHub Copilot Actually Saves Time in AL Projects

📇 Author: Steven Renders
🗓️ Date: March 12, 2026
🌎 Link: thinkaboutit.be
📝 Summary: Steven walks through each Copilot mode (Agent, Chat, Ask, Edit) with concrete AL examples — generating codeunit boilerplate, scaffolding test codeunits with automatic app.json dependency resolution, exploring HttpClient patterns via Chat, and refactoring procedures with Edit mode’s diff view. The post is candid about limits: Copilot occasionally produces incorrect AL syntax and struggles with complex business rules, posting routines, and dimension handling.


➡️ 5. Designing Agents for Business Central

📇 Author: AJ Kauffmann
🗓️ Date: March 13, 2026
🌎 Link: kauffmann.nl
📝 Summary: AJ clarifies what the Agent Design Experience (formerly Agent Builder Playground) actually is — a sandbox simulation tool for prototyping agents, not a production deployment platform. The post covers the full conversion workflow: export the agent as XML, create a new AL project with the Agent template, feed both to a coding agent, and publish the resulting extension. Tips include enabling the edit-instructions page in your agent app and extending the ConfigurationDialog setup page with additional groups.


➡️ 6. Context Engineering – The Thing Almost Nobody Is Actually Talking About

📇 Author: Vjeko
🗓️ Date: March 14, 2026
🌎 Link: vjeko.com
📝 Summary: Vjeko argues that context engineering — not prompt engineering or model selection — is the real lever for AI-assisted AL development. Referencing Andrej Karpathy and Anthropic, the post makes a BC-specific case: models lack implicit Business Central training data, a single codeunit like Sales-Post consumes ~158K tokens, and “context rot” from bloated context windows degrades output quality. The takeaway: curate precise, focused context instead of dumping entire projects into agent prompts.


➡️ 7. Exploring Model Context Protocol (MCP) for Business Automation in Business Central

📇 Author: Marcel Chabot
🗓️ Date: March 13, 2026
🌎 Link: aardvarklabs.blog
📝 Summary: Marcel walks through Business Central’s MCP Server Configuration page — connecting it to Copilot Studio, selecting API entities (Items, Customers, Sales Quotes), and assigning Read, Create, Modify, and Bound Actions permissions for each. The post covers end-to-end agent setup: configuring the MCP connection, testing conversational item queries through Copilot Studio, and security considerations like building purpose-specific APIs that limit exposed fields rather than surfacing standard APIs wholesale.


➡️ 8. Allow Posting From/To DateFormula: No More Monthly Date Updates from Business Central v28

📇 Author: Mohana
🗓️ Date: March 13, 2026
🌎 Link: mohana-dynamicsnav.blogspot.com
📝 Summary: BC28 adds Allow Posting From DateFormula and Allow Posting To DateFormula fields to General Ledger Setup and User Setup — standard DateFormula types processed via CalcDate that automatically calculate posting windows each period. The fields are mutually exclusive with the existing static Date fields: setting one clears the other via validate triggers. Example: set <-CM> / <CM> for automatic monthly posting windows.


➡️ 9. AL Language Extension for Microsoft Dynamics 365 Business Central – Versions

📇 Author: Gerardo Rentería
🗓️ Date: March 8, 2026
🌎 Link: gerardorenteria.blog
📝 Summary: Gerardo maintains a comprehensive reference page tracking every AL Language extension version from 15.0 through 18.0. The latest v16.0 entry covers key additions including the MaskType enum property for concealed field display, the UserControlHost PageType, ALTool compile command support for CI/CD pipelines, editable fields in page customizations with AllowInCustomizations property values, and the new Summary system part for Card and Document pages.


Community Resources

Official Resources

GitHub Repositories

  • microsoft/BCApps – Repository for collaboration on Microsoft Dynamics 365 Business Central applications.
  • microsoft/BCTech – Business Central technology samples.
  • microsoft/ALAppExtensions – Repository for collaboration on Microsoft AL application add-on and localization extensions for Microsoft Dynamics 365 Business Central.
  • microsoft/AL – Home of the Dynamics 365 Business Central AL Language extension for Visual Studio Code.
  • StefanMaron/MSDyn365BC.Code.History – Contains the Microsoft Business Central Code. Updated each month.

Follow on Social Media


Stay Connected

The Business Central AL development community stays active with valuable content on AL development, upgrades, integrations, and tooling improvements. Following #MSDyn365BC and #BusinessCentral on Twitter/X is a great way to catch new posts as they’re published.


Note: This review is compiled from publicly available blog posts and community resources. Links to external blog posts are provided for your information only and do not constitute endorsement or validation of their content. Publication information and availability are subject to change. Always verify information against official documentation for production use.

Permanent link to this article: https://www.dvlprlife.com/2026/03/weekly-review-business-central-al-development-march-8-14-2026/

March 2026 Cumulative Updates for Dynamics 365 Business Central

The March updates for Microsoft Dynamics 365 Business Central are now available.

Before applying the updates, you should confirm that your implementation is ready for the upgrade and ensure compatibility with your modifications. Work with a Microsoft Partner to determine if you are ready and what is needed for you to apply the update.

Please note that Online customers will automatically be upgraded to version 27.5 over the coming days/weeks and should receive an email notification when upgraded.

Direct links to the cumulative updates are listed here:

Dynamics 365 Business Central On-Premises 2025 Release Wave 2 – 27.5 (March 2026)

Dynamics 365 Business Central On-Premises 2025 Release Wave 1 – 26.11 (March 2026)

Dynamics 365 Business Central On-Premises 2024 Release Wave 2 – 25.17 (March 2026)

Dynamics 365 Business Central On-Premises 2024 Release Wave 1 – 24.18 (October 2025)

 


If you’re looking for information on older updates, review the list here.

Permanent link to this article: https://www.dvlprlife.com/2026/03/march-2026-cumulative-updates-for-dynamics-365-business-central/

Weekly Review: Business Central AL Development – March 1–7, 2026

Highlighting posts and resources from the Business Central development community — March 1–7, 2026

A strong week for the BC community — agents dominated the conversation again with posts covering everything from converting playground prototypes to production AL extensions, to orchestrating MCP tools via Copilot skills. We also saw Vjeko make a tough but transparent decision about AL Object ID Ninja’s self-hosting model, a new one-click performance analysis tool land inside Business Central, and the March 2026 cumulative update roundup.


Recent Posts (March 1–7, 2026)

➡️ 1. From Playground to AL Code: How to Convert an Agent into an Extension for Business Central

📇 Author: Javi Armesto
🗓️ Date: March 7, 2026
🌎 Link: techspheredynamics.com
📝 Summary: Javi maps the full architecture for converting a Business Central Agent Playground prototype into a distributable AL extension — centering on the enumextension that extends Agent Metadata Provider and connects three interfaces (IAgentFactory, IAgentMetadata, IAgentTaskExecution). The post covers the three-layer security model (Permission Set, Profile, Page Customizations), the ConfigurationDialog with SourceTableTemporary = true for reversible configuration, and proactive agent creation via EventSubscriber plus the AgentTaskBuilder fluent API. A complete conversion checklist ties it all together.


➡️ 2. Creating MCP Servers for Dynamics 365 Business Central with Azure Logic Apps

📇 Author: Stefano Demiliani
🗓️ Date: March 3, 2026
🌎 Link: demiliani.com
📝 Summary: Stefano walks through creating an MCP server for Business Central using Azure Logic Apps Standard — from selecting the Dynamics 365 Business Central connector and adding workflow tools, to configuring key-based authentication and connecting the server to GitHub Copilot in VS Code via mcp.json. Each connector action becomes a discoverable MCP tool that agents can call securely, giving you a scalable, low-code path to expose BC data to AI clients.


➡️ 3. AL Object ID Ninja: An Apology, a Decision, and a Month on the House

📇 Author: Vjeko
🗓️ Date: March 5, 2026
🌎 Link: vjeko.com
📝 Summary: Vjeko announces that private backend support for AL Object ID Ninja is being removed after discovering that a company was bundling access to a Ninja-compatible backend as a perk of their own paid offering — effectively undercutting the commercial service that funds Ninja’s development. The source code is now closed, and self-hosted connections will stop working in an upcoming extension update. Current private backend users get a one-month grace period on the commercial platform, or can freeze on their current extension version.


➡️ 4. One-Click Performance Analysis Inside Business Central

📇 Author: SShadowS
🗓️ Date: March 3, 2026
🌎 Link: blog.sshadows.dk
📝 Summary: SShadowS releases a BC extension that adds an “Analyze with AL Perf” button directly to the Performance Profiler page — one click sends the profile data to the AL Perf Analyzer service, which rebuilds the call tree and runs pattern detectors for single method dominance, repeated sibling calls (N+1), event subscriber hotspots, and deep call stacks. An AI layer (Claude) then generates a plain-English summary with root causes ranked by impact. The VS Code path with snapshot .zip files adds source correlation to prove syntactic nesting and flag missing SetLoadFields.


➡️ 5. Agent Skills + MCPs: Check Your BC Extension Health in VS Code

📇 Author: Gerardo Rentería
🗓️ Date: March 7, 2026
🌎 Link: gerardorenteria.blog
📝 Summary: Gerardo builds a reusable GitHub Copilot agent skill (.github/skills/bc-extension-health/) that orchestrates YAMPI and BC Telemetry Buddy to diagnose extension health across all tenant environments — reading app.json for the extension ID, querying YAMPI for installed versions, then running a KQL query against Application Insights covering six lifecycle events (LC0010–LC0023). A single chat message triggers the full flow and produces a unified Markdown report with environment status tables.


➡️ 6. Cumulative Update Summary for Microsoft Dynamics 365 Business Central (March 2026)

📇 Author: Yun Zhu
🗓️ Date: March 7, 2026
🌎 Link: yzhums.com
📝 Summary: Yun Zhu’s monthly CU tracker is updated for March 2026 — BC28 public preview is available (Platform 28.0.46613.0 + Application 28.0.46665.46791), BC27 is on CU05 (W1 27.5), and BC24 reaches end of life with CU18. The post includes direct links to release notes, download pages, and the BC admin center for each version still in support.


Community Resources

Official Resources

GitHub Repositories

  • microsoft/BCApps – Repository for collaboration on Microsoft Dynamics 365 Business Central applications.
  • microsoft/BCTech – Business Central technology samples.
  • microsoft/ALAppExtensions – Repository for collaboration on Microsoft AL application add-on and localization extensions for Microsoft Dynamics 365 Business Central.
  • microsoft/AL – Home of the Dynamics 365 Business Central AL Language extension for Visual Studio Code.
  • StefanMaron/MSDyn365BC.Code.History – Contains the Microsoft Business Central Code. Updated each month.

Follow on Social Media


Stay Connected

The Business Central AL development community stays active with valuable content on AL development, upgrades, integrations, and tooling improvements. Following #MSDyn365BC and #BusinessCentral on Twitter/X is a great way to catch new posts as they’re published.


Note: This review is compiled from publicly available blog posts and community resources. Links to external blog posts are provided for your information only and do not constitute endorsement or validation of their content. Publication information and availability are subject to change. Always verify information against official documentation for production use.

Permanent link to this article: https://www.dvlprlife.com/2026/03/weekly-review-business-central-al-development-march-1-7-2026/

Reading an XML File in Business Central (AL)

If you’ve worked with integrations in Business Central, chances are you’ve had to deal with XML at some point. In a previous post I covered how to create an XML file in AL. This time we’ll go the other direction—reading and parsing an XML document using the built-in XML data types in AL.

XML Reader Sample

We’ll work through a realistic example: reading a multi-level XML file that contains a sales order header with one or more sales lines.

You can find the full code for the example on GitHub.

The XML Data Types You Need to Know

AL ships with a set of XML data types purpose-built for reading and writing XML. For parsing an XML document you’ll primarily use these:

  • XmlDocument – represents the entire XML document. This is your entry point.
  • XmlElement – represents a single element (tag) in the document.
  • XmlNode – a more general type that can be an element, attribute, text, etc. You’ll often cast nodes to elements.
  • XmlNodeList – a collection of child nodes returned by methods like GetChildNodes() or GetChildElements().
  • XmlAttribute – represents an attribute on an element (e.g., currency="USD").
  • XmlDeclaration – represents the <?xml ...?> declaration at the top of the document.
  • XmlNamespaceManager – manages namespace lookups when your XML uses namespaces.

For most parsing tasks, XmlDocument, XmlElement, XmlNode, and XmlNodeList are all you need.

The Sample XML

Here’s the XML we’ll parse. It represents a sales order with a header and two lines:

<?xml version="1.0" encoding="utf-8"?>
<SalesOrder>
  <Header>
    <OrderNo>SO-1001</OrderNo>
    <CustomerNo>C-10000</CustomerNo>
    <OrderDate>2026-03-01</OrderDate>
    <Currency>USD</Currency>
  </Header>
  <Lines>
    <Line>
      <LineNo>10000</LineNo>
      <ItemNo>ITEM-1000</ItemNo>
      <Description>Widget A</Description>
      <Quantity>5</Quantity>
      <UnitPrice>12.50</UnitPrice>
    </Line>
    <Line>
      <LineNo>20000</LineNo>
      <ItemNo>ITEM-2000</ItemNo>
      <Description>Widget B</Description>
      <Quantity>3</Quantity>
      <UnitPrice>25.00</UnitPrice>
    </Line>
  </Lines>
</SalesOrder>

This is a typical multi-level structure: a root <SalesOrder> element wrapping a single <Header> and a <Lines> collection containing multiple <Line> children.

Loading the XML Document

The first step is getting the XML text into an XmlDocument. You can load it from a text variable, an InStream, or from a BLOB field—whatever your source happens to be. Here’s the most common pattern using XmlDocument.ReadFrom():

procedure ReadSalesOrderXml(XmlText: Text)
var
    XmlDoc: XmlDocument;
begin
    if not XmlDocument.ReadFrom(XmlText, XmlDoc) then
        Error('Failed to parse XML document.');

    ProcessSalesOrder(XmlDoc);
end;

ReadFrom() returns true if the XML was well-formed and parsed successfully, which makes it easy to handle malformed input up front. You can also load from an InStream:

procedure ReadSalesOrderFromStream(XmlStream: InStream)
var
    XmlDoc: XmlDocument;
begin
    XmlDocument.ReadFrom(XmlStream, XmlDoc);
    ProcessSalesOrder(XmlDoc);
end;

Navigating to the Root Element

Once you have an XmlDocument, grab the root element with GetRoot():

procedure ProcessSalesOrder(XmlDoc: XmlDocument)
var
    RootElement: XmlElement;
begin
    XmlDoc.GetRoot(RootElement);

    // RootElement is now <SalesOrder>
    ReadHeader(RootElement);
    ReadLines(RootElement);
end;

From the root element you can drill into child elements by name using SelectSingleNode() or by iterating with GetChildElements().

Reading the Header

To pull values from the <Header> element, use SelectSingleNode() to locate the header, then grab each child element’s inner text:

procedure ReadHeader(RootElement: XmlElement)
var
    HeaderNode: XmlNode;
    HeaderElement: XmlElement;
    OrderNo: Code[20];
    CustomerNo: Code[20];
    OrderDate: Date;
    Currency: Code[10];
begin
    if not RootElement.SelectSingleNode('Header', HeaderNode) then
        Error('Header element not found.');

    HeaderElement := HeaderNode.AsXmlElement();

    OrderNo := CopyStr(GetElementValue(HeaderElement, 'OrderNo'), 1, MaxStrLen(OrderNo));
    CustomerNo := CopyStr(GetElementValue(HeaderElement, 'CustomerNo'), 1, MaxStrLen(CustomerNo));
    Evaluate(OrderDate, GetElementValue(HeaderElement, 'OrderDate'));
    Currency := CopyStr(GetElementValue(HeaderElement, 'Currency'), 1, MaxStrLen(Currency));

    Message('Order: %1, Customer: %2, Date: %3, Currency: %4',
        OrderNo, CustomerNo, OrderDate, Currency);
end;

A SelectSingleNode() call returns true and populates the node variable if a match is found, so you can check whether the node exists before using it. Once you have the node, convert it to an XmlElement with AsXmlElement() so you can work with element-specific methods.

A Handy Helper: GetElementValue

You’ll find yourself reading inner text from child elements constantly, so a small helper keeps things clean:

procedure GetElementValue(ParentElement: XmlElement; ChildName: Text) ReturnValue: Text
var
    ChildNode: XmlNode;
begin
    if ParentElement.SelectSingleNode(ChildName, ChildNode) then
        ReturnValue := ChildNode.AsXmlElement().InnerText();
end;

This returns the text content of a named child element, or an empty string if the element isn’t found. Adjust this to throw an error or return a default depending on whether the element is required in your scenario.

Reading the Lines

Here’s where the multi-level structure comes in. The <Lines> element contains multiple <Line> children, so we need to iterate:

procedure ReadLines(RootElement: XmlElement)
var
    LinesNode: XmlNode;
    LinesElement: XmlElement;
    LineNodeList: XmlNodeList;
    LineNode: XmlNode;
    LineElement: XmlElement;
    LineNo: Integer;
    ItemNo: Code[20];
    Description: Text[100];
    Quantity: Decimal;
    UnitPrice: Decimal;
begin
    if not RootElement.SelectSingleNode('Lines', LinesNode) then
        Error('Lines element not found.');

    LinesElement := LinesNode.AsXmlElement();
    LineNodeList := LinesElement.GetChildElements('Line');

    foreach LineNode in LineNodeList do begin
        LineElement := LineNode.AsXmlElement();

        Evaluate(LineNo, GetElementValue(LineElement, 'LineNo'));
        ItemNo := CopyStr(GetElementValue(LineElement, 'ItemNo'), 1, MaxStrLen(ItemNo));
        Description := CopyStr(GetElementValue(LineElement, 'Description'), 1, MaxStrLen(Description));
        Evaluate(Quantity, GetElementValue(LineElement, 'Quantity'));
        Evaluate(UnitPrice, GetElementValue(LineElement, 'UnitPrice'));

        Message('Line %1: Item %2 (%3) - Qty %4 @ %5',
            LineNo, ItemNo, Description, Quantity, UnitPrice);
    end;
end;

Key points:

  • GetChildElements('Line') returns an XmlNodeList containing only the <Line> child elements.
  • The foreach loop walks each <Line> node.
  • We convert each node to an XmlElement and then use the same GetElementValue helper to pull values.

Dynamically Reading Child Elements

If you don’t know the child element names ahead of time, you can iterate all children of a node:

local procedure ReadHeader(RootElement: XmlElement; var OutputLines: List of [Text])
    var
        HeaderNode: XmlNode;
        HeaderElement: XmlElement;
        ChildNodes: XmlNodeList;
        ChildNode: XmlNode;
        ChildElement: XmlElement;
    begin
        if not RootElement.SelectSingleNode('Header', HeaderNode) then
            Error('Header element not found.');

        HeaderElement := HeaderNode.AsXmlElement();
        ChildNodes := HeaderElement.GetChildElements();

        OutputLines.Add('--- Header ---');
        foreach ChildNode in ChildNodes do begin
            ChildElement := ChildNode.AsXmlElement();
            OutputLines.Add(ChildElement.Name() + ': ' + ChildElement.InnerText());
        end;
    end;

    local procedure ReadLines(RootElement: XmlElement; var OutputLines: List of [Text])
    var
        LinesNode: XmlNode;
        LinesElement: XmlElement;
        LineNodeList: XmlNodeList;
        LineNode: XmlNode;
        LineElement: XmlElement;
        ChildNodes: XmlNodeList;
        ChildNode: XmlNode;
        ChildElement: XmlElement;
    begin
        if not RootElement.SelectSingleNode('Lines', LinesNode) then
            Error('Lines element not found.');

        LinesElement := LinesNode.AsXmlElement();
        LineNodeList := LinesElement.GetChildElements('Line');

        foreach LineNode in LineNodeList do begin
            LineElement := LineNode.AsXmlElement();
            ChildNodes := LineElement.GetChildElements();

            OutputLines.Add('--- Line ---');
            foreach ChildNode in ChildNodes do begin
                ChildElement := ChildNode.AsXmlElement();
                OutputLines.Add(ChildElement.Name() + ': ' + ChildElement.InnerText());
            end;
        end;
    end;

Putting It All Together

Here’s the full Codeunit so you can see how all the pieces connect:

Codeunit 50100 "Read Sales Order XML"
{
    procedure ReadSalesOrderXml(XmlText: Text)
    var
        XmlDoc: XmlDocument;
    begin
        if not XmlDocument.ReadFrom(XmlText, XmlDoc) then
            Error('Failed to parse XML document.');

        ProcessSalesOrder(XmlDoc);
    end;

    local procedure ProcessSalesOrder(XmlDoc: XmlDocument)
    var
        RootElement: XmlElement;
    begin
        XmlDoc.GetRoot(RootElement);
        ReadHeader(RootElement);
        ReadLines(RootElement);
    end;

    local procedure ReadHeader(RootElement: XmlElement)
    var
        HeaderNode: XmlNode;
        HeaderElement: XmlElement;
        OrderNo: Code[20];
        CustomerNo: Code[20];
        OrderDate: Date;
        Currency: Code[10];
    begin
        if not RootElement.SelectSingleNode('Header', HeaderNode) then
            Error('Header element not found.');

        HeaderElement := HeaderNode.AsXmlElement();

        OrderNo := CopyStr(GetElementValue(HeaderElement, 'OrderNo'), 1, MaxStrLen(OrderNo));
        CustomerNo := CopyStr(GetElementValue(HeaderElement, 'CustomerNo'), 1, MaxStrLen(CustomerNo));
        Evaluate(OrderDate, GetElementValue(HeaderElement, 'OrderDate'));
        Currency := CopyStr(GetElementValue(HeaderElement, 'Currency'), 1, MaxStrLen(Currency));

        Message('Order: %1, Customer: %2, Date: %3, Currency: %4',
            OrderNo, CustomerNo, OrderDate, Currency);
    end;

    local procedure ReadLines(RootElement: XmlElement)
    var
        LinesNode: XmlNode;
        LinesElement: XmlElement;
        LineNodeList: XmlNodeList;
        LineNode: XmlNode;
        LineElement: XmlElement;
        LineNo: Integer;
        ItemNo: Code[20];
        Description: Text[100];
        Quantity: Decimal;
        UnitPrice: Decimal;
    begin
        if not RootElement.SelectSingleNode('Lines', LinesNode) then
            Error('Lines element not found.');

        LinesElement := LinesNode.AsXmlElement();
        LineNodeList := LinesElement.GetChildElements('Line');

        foreach LineNode in LineNodeList do begin
            LineElement := LineNode.AsXmlElement();

            Evaluate(LineNo, GetElementValue(LineElement, 'LineNo'));
            ItemNo := CopyStr(GetElementValue(LineElement, 'ItemNo'), 1, MaxStrLen(ItemNo));
            Description := CopyStr(GetElementValue(LineElement, 'Description'), 1, MaxStrLen(Description));
            Evaluate(Quantity, GetElementValue(LineElement, 'Quantity'));
            Evaluate(UnitPrice, GetElementValue(LineElement, 'UnitPrice'));

            Message('Line %1: Item %2 (%3) - Qty %4 @ %5',
                LineNo, ItemNo, Description, Quantity, UnitPrice);
        end;
    end;

    local procedure GetElementValue(ParentElement: XmlElement; ChildName: Text) ReturnValue: Text
    var
        ChildNode: XmlNode;
    begin
        if ParentElement.SelectSingleNode(ChildName, ChildNode) then
            ReturnValue := ChildNode.AsXmlElement().InnerText();
    end;
}

Working with Attributes

Sometimes values live in attributes rather than child elements. For example, if <Header> had a currency attribute instead of a child element:

<Header currency="USD">

You’d read it using the Attributes() collection:

var
    AttrCollection: XmlAttributeCollection;
    CurrencyAttr: XmlAttribute;
    CurrencyValue: Text;
    i: Integer;
begin
    AttrCollection := HeaderElement.Attributes();
    for i := 1 to AttrCollection.Count do
        if AttrCollection.Get(i, CurrencyAttr) then
            if CurrencyAttr.Name() = 'currency' then begin
                CurrencyValue := CurrencyAttr.Value();
                break;
            end;
end;

Alternatively, you can use an XPath expression with SelectSingleNode() to target an attribute directly, which is often cleaner:

var
    AttrNode: XmlNode;
    CurrencyValue: Text;
begin
    if HeaderElement.SelectSingleNode('@currency', AttrNode) then
        CurrencyValue := AttrNode.AsXmlAttribute().Value();
end;

Handling Namespaces

If your XML uses namespaces (common with SOAP responses, e-invoicing standards like PEPPOL/UBL, and bank file formats like ISO 20022), you’ll need an XmlNamespaceManager. Here’s a quick example:

var
    XmlDoc: XmlDocument;
    NsMgr: XmlNamespaceManager;
    RootElement: XmlElement;
    ResultNode: XmlNode;
begin
    XmlDocument.ReadFrom(XmlText, XmlDoc);
    NsMgr.NameTable(XmlDoc.NameTable());
    NsMgr.AddNamespace('so', 'http://example.com/salesorder');

    XmlDoc.GetRoot(RootElement);
    RootElement.SelectSingleNode('so:Header', NsMgr, ResultNode);
end;

The namespace prefix you add to the manager ('so') doesn’t have to match the prefix in the XML—it just needs to map to the same URI.

Gotchas and Tips

  • Always check the return value of ReadFrom(). If the XML is malformed you’ll get false rather than a runtime error, which gives you a chance to log the problem and handle it gracefully.
  • SelectSingleNode() vs GetChildElements() vs SelectNodes(): Use SelectSingleNode() when you expect exactly one match by name. Use GetChildElements() when you expect zero or more children to iterate. Use SelectNodes() when you need XPath expressions that match multiple nodes at varying depths—it returns an XmlNodeList just like GetChildElements().
  • XmlReadOptions: ReadFrom() has overloads that accept an XmlReadOptions parameter. If you need to preserve whitespace in the document (for example, when processing pre-formatted text content), set XmlReadOptions.PreserveWhitespace to true before calling ReadFrom().
  • Type conversions: InnerText() always returns Text. Use Evaluate() to convert to Integer, Decimal, Date, etc. Watch out for locale-sensitive formats with dates and decimals—consider using XmlConvert or explicit format strings when parsing dates.
  • Large documents: For very large XML files, consider processing nodes as you go rather than loading everything into variables. AL’s XML types handle the DOM in memory, so extremely large files can affect performance.
  • Missing elements: Decide early whether a missing element is an error or just an empty value. The GetElementValue helper above silently returns empty text—adjust this for your requirements.

Wrapping Up

Reading XML in AL follows a straightforward pattern: load the document with XmlDocument.ReadFrom(), grab the root with GetRoot(), and then navigate the tree using SelectSingleNode() and GetChildElements(). A small helper like GetElementValue keeps your code clean when you’re pulling lots of values. For multi-level structures like our sales header/lines example, it’s just a matter of nesting the same pattern—navigate to the parent, iterate the children.

If you haven’t already, check out my earlier post on creating an XML file in AL to see the other side of the coin.

Learn more:

Note: The code and information discussed in this article are for informational and demonstration purposes only. Always test in a sandbox first. This content was written referencing Microsoft Dynamics 365 Business Central 2025 Wave 2 online.

Permanent link to this article: https://www.dvlprlife.com/2026/03/reading-an-xml-file-in-business-central-al/

Quick Tips: Keyboard Shortcuts in VS Code

Welcome to Quick Tips — a fast, focused series designed to help you work smarter.

Each post will give you one practical insight you can apply immediately, whether you’re coding, configuring your tools, or improving your workflow.

Here’s today’s Quick Tip:

Look Up Existing Shortcuts

VS Code has an abundant number of keyboard shortcuts built in, and every extension you install can add more. The Keyboard Shortcuts editor is the fastest way to find what’s already mapped.

Keyboard Shortcuts

  • Open it with Cmd+K Cmd+S on Mac or Ctrl+K Ctrl+S on Windows/Linux
  • Type a command name (like “toggle sidebar”) to find its shortcut
  • Type a key combination to see what’s bound to it
  • Right-click any entry and select Show Same Keybindings to see if other commands share the same shortcut

Understanding the Columns

The Keyboard Shortcuts editor displays each binding as a row with these columns:

  • Command — The human-readable name of the action (e.g., “Toggle Terminal”).
  • Keybinding — The key combination currently assigned. If blank, the command has no shortcut yet.
  • When — An optional context condition that must be true for the shortcut to fire (e.g., editorTextFocus).
  • Source — Where the binding came from: Default, User (your customizations), or Extension.

These columns make it easy to scan for conflicts, spot which shortcuts you’ve overridden, and see exactly when a binding is active.

Create Your Own Shortcuts

You can add or change a shortcut directly from the Keyboard Shortcuts editor:

  • Search for the command you want to bind.
  • Click the pencil icon (or double-click the entry).
  • Press the key combination you want and hit Enter.

For more control, open keybindings.json by clicking the Open Keyboard Shortcuts (JSON) button in the top-right corner of the editor. This lets you define rules with optional when clauses to scope shortcuts to specific contexts:

{
  "key": "ctrl+shift+t",
  "command": "workbench.action.terminal.toggleTerminal",
  "when": "editorTextFocus"
}

Detect and Resolve Conflicts

When you install several extensions, it’s common for two or more to claim the same key combination. When that happens, only one command wins and the others silently stop working.

To spot conflicts, right-click any shortcut in the Keyboard Shortcuts editor and select Show Same Keybindings. You can also run the command Developer: Toggle Keyboard Shortcuts Troubleshooting from the Command Palette to log exactly which rule fires when you press a key.

Why Customize Your Shortcuts

  • Resolve extension conflicts — Two extensions fighting over the same keys? Reassign one so both work.
  • Match your muscle memory — Coming from Vim, Sublime, or another editor? Remap keys to feel at home (or install a Keymap extension).
  • Add shortcuts to unbound commands — Many useful commands don’t have a default shortcut. Give them one.
  • Scope shortcuts to specific contexts — Use when clauses to make a shortcut active only in the terminal, a specific language, or during debugging.
  • Improve ergonomics — Replace awkward three-key combos with something that’s easier on your hands.

Learn more about keyboard shortcuts in VS Code.

Got a favorite shortcut or workflow tweak? Share it in the comments and subscribe to dvlprlife.com for more Quick Tips like this one!

Permanent link to this article: https://www.dvlprlife.com/2026/03/quick-tips-keyboard-shortcuts-in-vs-code/

Weekly Review: Business Central AL Development – February 22–28, 2026

Highlighting posts and resources from the Business Central development community — February 22–28, 2026

Looking to stay current with Dynamics 365 Business Central AL development? Here’s a curated list of recent blog posts, tutorials, and community resources from the past week.


Recent Posts (February 22–28, 2026)

➡️ 1. BC Online Wait Statistics in Telemetry: The AI Boost

📇 Author: Duilio Tacconi
🗓️ Date: February 23, 2026
🌎 Link: duiliotacconi.com
📝 Summary: Duilio does a deep technical exploration of wait statistics in Business Central Online telemetry — from the underlying DMV query that collects the data, through KQL delta analysis during a BCPT load test that surfaced lock contention as 46% of total wait time, to feeding the results to AI for automated interpretation. The post includes practical T-SQL and KQL examples, plus a downloadable AI-generated analysis document showing how GitHub Copilot with Claude can turn raw telemetry into actionable performance insights.


➡️ 2. AL Performance Bootcamp: Level Up Your Code — BCTechDays 2026 Workshop

📇 Author: Stefan Šošić
🗓️ Date: February 23, 2026
🌎 Link: ssosic.com
📝 Summary: Stefan announces his upcoming BCTechDays 2026 workshop focused on AL performance optimization. The hands-on session promises practical techniques for identifying and resolving performance bottlenecks in Business Central AL code — a timely complement to the growing community interest in BC performance tooling, telemetry analysis, and background processing patterns.


➡️ 3. Troubleshooting Series – Ep5 – Telemetry

📇 Author: Waldo
🗓️ Date: February 24, 2026
🌎 Link: waldo.be
📝 Summary: Waldo dedicates episode 5 of his troubleshooting series to Business Central telemetry — covering the two telemetry channels (environment-level and app/extension-level), Microsoft’s built-in tooling (Power BI apps, BCTech repo KQL queries, the 15-minute “Enable Additional Logging” SQL profiler), and his own BC Telemetry Buddy MCP tool. The post traces his telemetry journey from Azure Data Explorer dashboards through AI-assisted querying, with practical guidance on getting started without needing to become a KQL expert.


➡️ 4. If You Can’t Make It Fast, Make It Feel Fast

📇 Author: Stefan Maron
🌎 Link: stefanmaron.com
📝 Summary: Stefan and Henrik Helgesen walk through Business Central’s four background processing tools — Page Background Tasks, StartSession, TaskScheduler, and Job Queue — anchored by a real-world case study from a medical device manufacturer where moving production planning to a Job Queue pattern eliminated deadlocks and delivered instant user feedback. The post also covers how BC Online’s auto-scaling distributes TaskScheduler and Job Queue work across server instances, with a comparison table showing when to reach for each tool.


➡️ 5. ALCops: The Next Chapter of LinterCop

📇 Author: Arthur van der Voort
🗓️ Date: February 23, 2026
🌎 Link: vondervoort.be
📝 Summary: Arthur shares the story behind ALCops from the maintainer’s perspective — born from a conversation with Stefan Maron about LinterCop’s future, the project was rebuilt from scratch to allow breaking changes without disrupting existing pipelines. The current v0.5.0 release has ~95% of LinterCop rules migrated, a VS Code extension, and AL-Go support, with Azure DevOps integration and AI tooling exploration (including a potential ALCops MCP server) still on the roadmap. BusinessCentral.LinterCop will be set to read-only in the coming months.


➡️ 6. Learn AL with Claude — Interactive Business Central Development Course

📇 Author: Silviu Virlan
🗓️ Date: February 24, 2026
🌎 Link: svirlan.com
📝 Summary: Silviu introduces an interactive AL development course powered by Claude Code — 13 lesson scripts that run inside VS Code using a marker system (STOP, USER, ACTION) to create an adaptive teaching flow. The course follows a continuous scenario where you build an Equipment Rental Management extension for a fictional outdoor equipment company, covering tables, pages, enums, codeunits, reports, and permission sets across two modules.


➡️ 7. A Practical Guide to Creating Agents in Business Central with AI Development Toolkit (Preview)

📇 Author: Javi Armesto
🗓️ Date: February 26, 2026
🌎 Link: techspheredynamics.com
📝 Summary: Javi presents a comprehensive 5-phase framework for building Business Central agents using the AI Development Toolkit preview — covering identity and metadata, the AL skeleton (IAgentFactory, IAgentMetadata, IAgentTaskExecution), natural language instructions with official toolkit keywords like MEMORIZE and Reply, security layering through permissions, profiles, and page customizations, and ConfigurationDialog setup. The post also explores proactive task creation via event subscribers and the Agent Task Builder fluent API, all anchored by a working HR Absence Agent example.


➡️ 8. Why API Templates Don’t Work on Business Central Custom API Pages

📇 Author: Mohana
🗓️ Date: February 28, 2026
🌎 Link: mohana-dynamicsnav.blogspot.com
📝 Summary: Mohana digs into why API templates silently fail on custom API pages — the standard ProcessNewRecordFromAPI trigger relies on TempFieldSet tracking to apply template values, but custom fields aren’t tracked in the same way. The post walks through the underlying system behavior with code references and demonstrates how to make API templates work correctly with custom API pages.


Recent Videos (February 22–28, 2026)

🎬 1. What Is New: Business Central Agent Instruction History

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: A walkthrough of the new instruction history feature for Business Central agents — covering how to save specific versions of agent instructions, browse and restore previous versions, and export them to disk. Part of the “Design and coding agents in Business Central” playlist.


🎬 2. What Is New: Understanding Copilot Credit Consumptions for Your Business Central Agent

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: A guide to monitoring and analyzing Copilot credit consumption for Business Central agents — showing how to get an overview of credit usage per task and per agent for a given time period. Part of the “Design and coding agents in Business Central” playlist.


🎬 3. What Is New: Coding Business Central Agents with AI Development Toolkit

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: Demonstrates how to create agents you can distribute via AL applications — defining your own agent type, configuring it properly, and integrating it with Business Central business processes using the AI Development Toolkit. Part of the “Design and coding agents in Business Central” playlist.


🎬 4. What Is New: Troubleshooting Business Central Agents

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: Shows how to unblock agents when they get stuck with an assigned task, covering practical troubleshooting steps for Business Central agents. Part of the “Design and coding agents in Business Central” playlist.


🎬 5. What Is New: Exporting and Importing Agent in Business Central

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: Walks through exporting and importing agents in Business Central during the design phase — useful for moving agent configurations between environments or sharing them across teams. Part of the “Design and coding agents in Business Central” playlist.


🎬 6. Working with Instructions for Agents in Business Central

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: Covers best strategies for writing agent instructions, running tasks, and testing the available instructions and tools. The second part of the video explains the specific tools that agents can use. Part of the “Design and coding agents in Business Central” playlist.


🎬 7. Sales Validation Sample Agent for Business Central

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: Shows how to use sample agents in Business Central for inspiration and examples when building your own. The demo walks through the Sales Validation agent, illustrating how sample agents can serve as a starting point for custom development. Part of the “Design and coding agents in Business Central” playlist.


🎬 8. How to Create Agents in Business Central

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: A getting-started guide to defining custom agents in Business Central — the demo walks through a quick end-to-end process of creating and running an agent within Business Central. Part of the “Design and coding agents in Business Central” playlist.


🎬 9. Introducing: Envision, Design and Code Business Central Agents

📺 Channel: Microsoft Dynamics 365 Business Central
🗓️ Date: February 27, 2026
🌎 Link: youtube.com
📝 Summary: An introductory overview of how to envision, design, and code powerful Business Central agents — setting the stage for the rest of the “Design and coding agents in Business Central” playlist.


Community Resources

Official Resources

GitHub Repositories

  • microsoft/BCApps – Repository for collaboration on Microsoft Dynamics 365 Business Central applications.
  • microsoft/BCTech – Business Central technology samples.
  • microsoft/ALAppExtensions – Repository for collaboration on Microsoft AL application add-on and localization extensions for Microsoft Dynamics 365 Business Central.
  • microsoft/AL – Home of the Dynamics 365 Business Central AL Language extension for Visual Studio Code.
  • StefanMaron/MSDyn365BC.Code.History – Contains the Microsoft Business Central Code. Updated each month.

Follow on Social Media


Stay Connected

The Business Central AL development community stays active with valuable content on AL development, upgrades, integrations, and tooling improvements. Following #MSDyn365BC and #BusinessCentral on Twitter/X is a great way to catch new posts as they’re published.


Note: This review is compiled from publicly available blog posts and community resources. Links to external blog posts are provided for your information only and do not constitute endorsement or validation of their content. Publication information and availability are subject to change. Always verify information against official documentation for production use.

Permanent link to this article: https://www.dvlprlife.com/2026/03/weekly-review-business-central-al-development-february-22-28-2026/

Quick Tips: InStream and OutStream in AL

Welcome to Quick Tips — a fast, focused series designed to help you work smarter.

Each post will give you one practical insight you can apply immediately, whether you’re coding, configuring your tools, or improving your workflow.

Here’s today’s Quick Tip:

InStream vs. OutStream — What’s the Difference?

If you’ve worked with BLOBs, files, or web service payloads in Business Central AL, you’ve almost certainly encountered InStream and OutStream. Many developers mix them up, so let’s clear it up once and for all.

  • InStream — A stream you read from. Data flows in to your code from a source (a BLOB, a file, an HTTP response, etc.).
  • OutStream — A stream you write to. Data flows out from your code into a target (a BLOB, a file, an HTTP request body, etc.).

Think of it from your AL code’s perspective: In means data is coming in to you. Out means data is going out from you.

Stream Example

A Creative Way to Remember

Picture a mailbox:

  • InStream is the mail you receive — you open the mailbox and read what’s inside.
  • OutStream is the letter you send — you write your message and put it out for delivery.

In = Read. Out = Write. That’s it.

Quick Example

Here’s a common pattern — writing text into a BLOB and then reading it back:

procedure InStreamOutStreamDemo()
var
    TempBlob: Codeunit "Temp Blob";
    OutStr: OutStream;
    InStr: InStream;
    Result: Text;
begin
    // Write TO the BLOB using OutStream
    TempBlob.CreateOutStream(OutStr, TextEncoding::UTF8);
    OutStr.WriteText('Hello from OutStream!');

    // Read FROM the BLOB using InStream
    TempBlob.CreateInStream(InStr, TextEncoding::UTF8);
    InStr.ReadText(Result);

    Message(Result); // 'Hello from OutStream!'
end;

Notice the pattern: CreateOutStream gives you a stream to write data into the BLOB, and CreateInStream gives you a stream to read data out of it.

Loading a Text File with InStream

A very common real-world scenario is letting a user upload a text file and reading its contents line by line:

procedure ImportTextFile()
var
    InStr: InStream;
    FileName: Text;
    Line: Text;
    FullContent: TextBuilder;
begin
    // Prompt the user to select a file — this gives us an InStream
    if not UploadIntoStream('Select a text file', '', 'Text Files (*.txt)|*.txt', FileName, InStr) then
        exit;

    // Read the file line by line until End of Stream
    while not InStr.EOS() do begin
        InStr.ReadText(Line);
        FullContent.AppendLine(Line);
    end;

    Message('File: %1\Contents:\%2', FileName, FullContent.ToText());
end;

Here UploadIntoStream hands you an InStream because the file data is flowing in to your code. You then loop with EOS() (End of Stream) and ReadText() to consume it line by line.

Import a File into a BLOB and Export It Back

This example ties both streams together — upload a file into a BLOB field using OutStream, then download it back out using InStream:

procedure ImportFileToBlobField(var Rec: Record MyTable)
var
    InStr: InStream;
    OutStr: OutStream;
    FileName: Text;
begin
    // Upload the file — UploadIntoStream gives us an InStream to READ from
    if not UploadIntoStream('Select a file', '', '', FileName, InStr) then
        exit;

    // Create an OutStream on the BLOB field to WRITE the file data into it
    Rec.BlobField.CreateOutStream(OutStr);
    CopyStream(OutStr, InStr);
    Rec.Modify(true);

    Message('File "%1" saved to the BLOB field.', FileName);
end;

procedure ExportBlobFieldToFile(var Rec: Record MyTable)
var
    InStr: InStream;
    FileName: Text;
begin
    Rec.CalcFields(BlobField);

    // Create an InStream on the BLOB field to READ the data back out
    Rec.BlobField.CreateInStream(InStr);
    FileName := 'ExportedFile.txt';
    DownloadFromStream(InStr, 'Export File', '', '', FileName);
end;

Notice how the directions stay consistent:

  • Importing: You read from the uploaded file (InStream) and write into the BLOB (OutStream).
  • Exporting: You read from the BLOB (InStream) and the download sends it out to the user.

CopyStream is a handy built-in that pipes an InStream directly into an OutStream so you don’t need to loop manually.

Key Methods at a Glance

InStreamOutStream
PurposeRead dataWrite data
TextReadText()WriteText()
BinaryRead()Write()
End of streamEOS()
PositionPosition(), ResetPosition()

You can find the full code for the example on GitHub.

Why It Helps

Once the direction clicks, you’ll stop second-guessing which stream type you need. Whether you’re importing a file upload, exporting a report to XML, or passing data between extensions via TempBlob, knowing that InStream = Read and OutStream = Write keeps your code correct on the first try.

Learn more about InStream and OutStream on Microsoft Learn.

Got a favorite shortcut or workflow tweak? Share it in the comments and subscribe to dvlprlife.com for more Quick Tips like this one!

Permanent link to this article: https://www.dvlprlife.com/2026/03/quick-tips-instream-and-outstream-in-al/

Working with the Type Helper Codeunit in Business Central (AL)

If you’ve spent any time in AL development, you’ve probably reinvented a wheel or two. Written a quick parser for a date string, wrestled with URL encoding for an API call, or built a little helper to compare dates safely. The "Type Helper" Codeunit (ID 10, namespace System.Reflection) exists specifically to handle this kind of utility work—and it’s surprisingly comprehensive.

Type Helper Codeunit Sample

This post walks through useful procedures grouped by category, with practical AL examples for each.

You can find the full code for the example on GitHub.


What Is the Type Helper Codeunit?

"Type Helper" is a base application Codeunit that provides a broad set of utility methods covering:

  • String and character checks
  • Date, time, and timezone formatting and conversion
  • URL/HTML encoding
  • Record and field reflection
  • Option field helpers
  • Stream reading
  • Amount/currency format strings

It’s not flashy, but it saves real time. Before writing your own utility procedure, check here first.


String Utilities

IsNumeric

procedure IsNumeric(Text: Text): Boolean

Returns true if a text value can be parsed as a Decimal.

var
    TypeHelper: Codeunit "Type Helper";
    Result1, Result2: Boolean;
    Value1, Value2: Text;
begin
    Value1 := '42.5';
    Value2 := 'hello';

    Result1 := TypeHelper.IsNumeric(Value1);   // true
    Result2 := TypeHelper.IsNumeric(Value2);  // false
    Message('IsNumeric(''' + Value1 + '''): %1\IsNumeric(''' + Value2 + '''): %2', Result1, Result2);
end;

Useful for validating user input or imported data before calling Evaluate.

IsPhoneNumber

procedure IsPhoneNumber(Input: Text): Boolean

Validates that a string contains only digits, spaces, parentheses, dashes, and +. It uses a regex under the hood.

var
    TypeHelper: Codeunit "Type Helper";
    Value1, Value2: Text;
begin
    Value1 := '+1 (555) 123-4567';
    Value2 := 'not-a-phone';

    Message('IsPhoneNumber(''' + Value1 + '''): %1\IsPhoneNumber(''' + Value2 + '''): %2',
        TypeHelper.IsPhoneNumber(Value1), TypeHelper.IsPhoneNumber(Value2));
end;

IsLatinLetter / IsDigit / IsUpper

procedure IsLatinLetter(ch: Char): Boolean
procedure IsDigit(ch: Char): Boolean
procedure IsUpper(ch: Char): Boolean

Character-level checks. Handy when parsing text character by character.

var
    TypeHelper: Codeunit "Type Helper";
    ch: Char;
begin
    ch := 'A';
    if TypeHelper.IsLatinLetter(ch) then
        Message('It is a letter.');

    ch := '3';
    if TypeHelper.IsDigit(ch) then
        Message('It is a digit.');
end;

TextDistance

procedure TextDistance(Text1: Text; Text2: Text): Integer

Calculates the Levenshtein distance (I didn’t know what this was before digging into this Codeunit) between two strings—the minimum number of single-character edits to get from one string to the other. Useful for fuzzy matching or detecting near-duplicates.

var
    TypeHelper: Codeunit "Type Helper";
    Distance: Integer;
begin
    Distance := TypeHelper.TextDistance('Business', 'Busines');  // 1
    Distance := TypeHelper.TextDistance('Hello', 'World');       // 4
end;

Note: Strings are limited to ~1024 characters.

NewLine / CRLFSeparator / LFSeparator

procedure NewLine(): Text
procedure CRLFSeparator(): Text[2]
procedure LFSeparator(): Text[1]

Returns the system newline string, a carriage-return + line-feed pair, or just a line feed. Avoid hardcoding character codes.

var
    TypeHelper: Codeunit "Type Helper";
    Msg: Text;
begin
    Msg := 'Line one' + TypeHelper.CRLFSeparator() + 'Line two';
    Message(Msg);
end;

Date and Time

FormatDateWithCurrentCulture

procedure FormatDateWithCurrentCulture(DateToFormat: Date): Text

The simplest way to format a Date using the current user’s locale. Great for displaying dates in messages or reports.

var
    TypeHelper: Codeunit "Type Helper";
    FormattedDate: Text;
begin
    FormattedDate := TypeHelper.FormatDateWithCurrentCulture(WorkDate());
    Message(FormattedDate);
end;

FormatDate (overloads)

procedure FormatDate(DateToFormat: Date; LanguageId: Integer): Text
procedure FormatDate(DateToFormat: Date; Format: Text; CultureName: Text): Text

Two overloads let you format with a specific language ID or a custom format string and culture name.

var
    TypeHelper: Codeunit "Type Helper";
begin
    // Format using a language ID (e.g., 1033 = en-US)
    Message(TypeHelper.FormatDate(WorkDate(), 1033));

    // Format using a .NET format string and culture name
    Message(TypeHelper.FormatDate(WorkDate(), 'MMMM dd, yyyy', 'en-US'));
end;

CompareDateTime

procedure CompareDateTime(DateTimeA: DateTime; DateTimeB: DateTime): Integer

Compares two DateTime values with a built-in millisecond-level tolerance that accounts for SQL precision rounding. Returns 1, 0, or -1.

var
    TypeHelper: Codeunit "Type Helper";
    StartDateTime: DateTime;
    EndDateTime: DateTime;
    Result: Integer;
begin
    StartDateTime := CreateDateTime(WorkDate(), 080000T);
    EndDateTime := CreateDateTime(WorkDate(), 170000T);

    Result := TypeHelper.CompareDateTime(StartDateTime, EndDateTime);
    Message('CompareDateTime result: %1 (negative means first is earlier)', Result);
end;

Don’t compare DateTime values directly with = when they come from the database—use this instead.

AddHoursToDateTime

procedure AddHoursToDateTime(SourceDateTime: DateTime; NoOfHours: Integer): DateTime

Adds a number of hours to a DateTime value.

var
    TypeHelper: Codeunit "Type Helper";
    NewDT: DateTime;
begin
    NewDT := TypeHelper.AddHoursToDateTime(CurrentDateTime, 8);
end;

GetHMSFromTime

procedure GetHMSFromTime(var Hour: Integer; var Minute: Integer; var Second: Integer; TimeSource: Time)

Breaks a Time value into its hour, minute, and second components.

var
    TypeHelper: Codeunit "Type Helper";
    H: Integer;
    M: Integer;
    S: Integer;
    CurrentTime: Time;
begin
    CurrentTime := Time;
    TypeHelper.GetHMSFromTime(H, M, S, CurrentTime);
    Message('Current time -> H:%1 M:%2 S:%3', H, M, S);
end;

EvaluateUnixTimestamp

procedure EvaluateUnixTimestamp(Timestamp: BigInteger): DateTime

Converts a Unix epoch timestamp (BigInteger, seconds since Jan 1 1970) to a DateTime, adjusting for the user’s timezone offset.

var
    TypeHelper: Codeunit "Type Helper";
    ResultDT: DateTime;
    TimeStamp: BigInteger;
begin
    TimeStamp := 1740657600;
    ResultDT := TypeHelper.EvaluateUnixTimestamp(TimeStamp);
    Message('EvaluateUnixTimestamp(' + Format(TimeStamp, 9) + '): %1', ResultDT);
end;

GetCurrUTCDateTime / GetCurrUTCDateTimeAsText / GetCurrUTCDateTimeISO8601

procedure GetCurrUTCDateTime(): DateTime
procedure GetCurrUTCDateTimeAsText(): Text
procedure GetCurrUTCDateTimeISO8601(): Text

Three shortcuts for getting the current UTC time in different formats.

var
    TypeHelper: Codeunit "Type Helper";
begin
    // DateTime value
    MyDT := TypeHelper.GetCurrUTCDateTime();

    // RFC 1123 text: "Thu, 27 Feb 2026 14:30:00 GMT"
    MyText := TypeHelper.GetCurrUTCDateTimeAsText();

    // ISO 8601: "2026-02-27T14:30:00Z"
    MyText := TypeHelper.GetCurrUTCDateTimeISO8601();
end;

GetCurrUTCDateTimeISO8601 is especially useful when building API payloads.


Timezone Conversions

GetUserTimezoneOffset

procedure GetUserTimezoneOffset(var Duration: Duration): Boolean

Returns the current user’s UTC offset as a Duration. Returns false if the user has no timezone set.

var
    TypeHelper: Codeunit "Type Helper";
    Offset: Duration;
begin
    if TypeHelper.GetUserTimezoneOffset(Offset) then
        Message('Offset in ms: %1', Offset);
end;

Type Conversion and Formatting

Evaluate

procedure Evaluate(var Variable: Variant; String: Text; Format: Text; CultureName: Text): Boolean

The general-purpose type evaluator. Pass in a Variant of the target type, a text string, an optional format, and an optional culture name.

var
    TypeHelper: Codeunit "Type Helper";
    Value: Variant;
    ParsedDate: Date;
    Ok: Boolean;
begin
    ParsedDate := 0D;
    Value := ParsedDate;
    Ok := TypeHelper.Evaluate(Value, '02/27/2026', 'MM/dd/yyyy', 'en-US');
    if Ok then
        ParsedDate := Value;
end;

FormatDecimal

procedure FormatDecimal(Decimal: Decimal; DataFormat: Text; DataFormattingCulture: Text): Text

Formats a Decimal with a .NET format string and culture name. Useful for building API payloads or localized output.

var
    TypeHelper: Codeunit "Type Helper";
    Formatted: Text;
begin
    Formatted := TypeHelper.FormatDecimal(1234.56, 'N2', 'en-US');  // "1,234.56"
    Formatted := TypeHelper.FormatDecimal(1234.56, 'N2', 'de-DE');  // "1.234,56"
end;

IntToHex

procedure IntToHex(IntValue: Integer): Text

Converts an integer to its hexadecimal representation.

var
    TypeHelper: Codeunit "Type Helper";
begin
    Message(TypeHelper.IntToHex(255));   // "FF"
    Message(TypeHelper.IntToHex(4096));  // "1000"
end;

Maximum / Minimum

procedure Maximum(Value1: Decimal; Value2: Decimal): Decimal
procedure Minimum(Value1: Decimal; Value2: Decimal): Decimal

Returns the larger or smaller of two Decimal values.

var
    TypeHelper: Codeunit "Type Helper";
begin
    Message('%1', TypeHelper.Maximum(10.0, 25.5));  // 25.5
    Message('%1', TypeHelper.Minimum(10.0, 25.5));  // 10
end;

Web and URL Encoding

These are essential any time you’re calling external APIs or generating web links in AL.

UrlEncode / UrlDecode

procedure UrlEncode(var Value: Text): Text
procedure UrlEncode(var Value: SecretText): SecretText
procedure UrlDecode(var Value: Text): Text
var
    TypeHelper: Codeunit "Type Helper";
    Encoded: Text;
begin
    Encoded := 'search term with spaces';
    TypeHelper.UrlEncode(Encoded);
    Message(Encoded);  // "search+term+with+spaces"
end;

There’s also an overload that accepts and returns SecretText for encoding credentials without exposing the raw value.

HtmlEncode / HtmlDecode

procedure HtmlEncode(var Value: Text): Text
procedure HtmlDecode(var Value: Text): Text

Encodes characters like <, >, & for safe HTML output, or decodes them back.

var
    TypeHelper: Codeunit "Type Helper";
    SafeHtml: Text;
begin
    SafeHtml := '<script>alert("xss")</script>';
    TypeHelper.HtmlEncode(SafeHtml);
    Message(SafeHtml);  // "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"
end;

UriEscapeDataString / UriGetAuthority

procedure UriEscapeDataString(Value: Text): Text
procedure UriGetAuthority(Value: Text): Text

UriEscapeDataString percent-encodes a value (RFC 3986). UriGetAuthority extracts the scheme + host from a URL.

var
    TypeHelper: Codeunit "Type Helper";
begin
    Message(TypeHelper.UriEscapeDataString('hello world'));  // "hello%20world"
    Message(TypeHelper.UriGetAuthority('https://api.example.com/v1/items'));
    // "https://api.example.com"
end;

Option Field Helpers

GetOptionNo

procedure GetOptionNo(Value: Text; OptionString: Text): Integer

Looks up the integer index of an option value string within an option set string. Returns -1 if not found.

var
    TypeHelper: Codeunit "Type Helper";
    Idx: Integer;
begin
    Idx := TypeHelper.GetOptionNo('Posted', 'Open,Released,Posted,Canceled');
    // Returns 2
end;

GetNumberOfOptions

procedure GetNumberOfOptions(OptionString: Text): Integer

Counts how many comma-separated options are in an option string.

var
    TypeHelper: Codeunit "Type Helper";
begin
    Message('%1', TypeHelper.GetNumberOfOptions('Open,Released,Posted'));  // 2 (counts commas)
end;

Watch out: this counts commas, so it returns n - 1 for n options. Adjust accordingly.


Record and Field Reflection

GetField / GetFieldLength

procedure GetField(TableNo: Integer; FieldNo: Integer; var Field: Record Field): Boolean
procedure GetFieldLength(TableNo: Integer; FieldNo: Integer): Integer

GetField retrieves field metadata from the Field virtual table and returns false if the field doesn’t exist or is obsolete (ObsoleteState = Removed). GetFieldLength returns the declared length of a text field.

var
    TypeHelper: Codeunit "Type Helper";
    FieldRec: Record Field;
begin
    if TypeHelper.GetField(Database::Customer, Customer.FieldNo(Name), FieldRec) then
        Message('Max length: %1', TypeHelper.GetFieldLength(Database::Customer, Customer.FieldNo(Name)));
end;

SortRecordRef

procedure SortRecordRef(var RecRef: RecordRef; CommaSeparatedFieldsToSort: Text; Ascending: Boolean)

Applies a sort order to a RecordRef using a comma-separated list of field names.

var
    TypeHelper: Codeunit "Type Helper";
    RecRef: RecordRef;
begin
    RecRef.Open(Database::Customer);
    TypeHelper.SortRecordRef(RecRef, '"No."', true);  // ascending by No.
    if RecRef.FindSet() then
        repeat
            // ...
        until RecRef.Next() = 0;
end;

GetKeyAsString

procedure GetKeyAsString(RecordVariant: Variant; KeyIndex: Integer): Text

Returns the key fields of a record as a comma-separated string. Useful for logging or telemetry.

var
    Cust: Record Customer;
    TypeHelper: Codeunit "Type Helper";
    KeyText: Text;
begin
    if Cust.FindFirst() then begin
        KeyText := TypeHelper.GetKeyAsString(Cust, 1);
        Message('GetKeyAsString: %1', KeyText);
    end;
end;

Stream Reading

ReadAsTextWithSeparator / TryReadAsTextWithSeparator

procedure ReadAsTextWithSeparator(InStream: InStream; LineSeparator: Text): Text
[TryFunction]
procedure TryReadAsTextWithSeparator(InStream: InStream; LineSeparator: Text; var Content: Text): Boolean

Reads an InStream into a Text value, using a specified line separator. TryReadAsTextWithSeparator wraps the call in a [TryFunction] for error-safe reading.

var
    TempBlob: Codeunit "Temp Blob";
    TypeHelper: Codeunit "Type Helper";
    FileContent: Text;
    IStream: InStream;
    OStream: OutStream;
begin
    TempBlob.CreateOutStream(OStream);
    OStream.WriteText('Line 1');
    OStream.WriteText(TypeHelper.CRLFSeparator());
    OStream.WriteText('Line 2');
    OStream.WriteText(TypeHelper.CRLFSeparator());
    OStream.WriteText('Line 3');

    TempBlob.CreateInStream(IStream);
    FileContent := TypeHelper.ReadAsTextWithSeparator(IStream, TypeHelper.CRLFSeparator());
    Message('ReadAsTextWithSeparator:\%1', FileContent);
end;

Amount and Currency Formatting

GetAmountFormatLCYWithUserLocale

procedure GetAmountFormatLCYWithUserLocale(): Text
procedure GetAmountFormatLCYWithUserLocale(DecimalPlaces: Integer): Text

Returns a BC format string for displaying amounts in the local currency, formatted for the current user’s locale. Pass an optional decimal places count.

var
    TypeHelper: Codeunit "Type Helper";
    AmtFormat: Text;
begin
    AmtFormat := TypeHelper.GetAmountFormatLCYWithUserLocale();
    Message(Format(1234.56, 0, AmtFormat));
end;

GetXMLAmountFormatWithTwoDecimalPlaces / GetXMLDateFormat

procedure GetXMLAmountFormatWithTwoDecimalPlaces(): Text
procedure GetXMLDateFormat(): Text

Return BC format strings suitable for XML output—two decimal places for amounts and the standard XML date format.

var
    TypeHelper: Codeunit "Type Helper";
begin
    Message(Format(Today, 0, TypeHelper.GetXMLDateFormat()));        // e.g., "2026-02-27"
    Message(Format(12345.6, 0, TypeHelper.GetXMLAmountFormatWithTwoDecimalPlaces()));  // "12345.60"
end;

Wrapping Up

The "Type Helper" codeunit is one of the most useful utility libraries in BC’s base application—and one of the most underused. Before writing your own string parser, encoding helper, or date workaround, it’s worth skimming the procedure list. There’s a good chance something here already does exactly what you need.

A few things to keep in mind:

  • LanguageIDToCultureName and GetCultureName are obsolete as of v26—Use Codeunit 43 Language instead.
  • UrlEncodeSecret is obsolete as of v27—Use the SecretText overload of UrlEncode instead.

Learn more: Codeunit “Type Helper” – Microsoft Learn


Note: The code and information discussed in this article are for informational and demonstration purposes only. Always test in a sandbox environment before deploying to production. This content was written referencing Microsoft Dynamics 365 Business Central 2025 Wave 2.

Permanent link to this article: https://www.dvlprlife.com/2026/02/working-with-the-type-helper-codeunit-in-business-central-al/

Weekly Review: Business Central AL Development – February 15–21, 2026

Highlighting posts and resources from the Business Central development community — February 15–21, 2026

Looking to stay current with Dynamics 365 Business Central AL development? Here’s a curated list of recent blog posts, tutorials, and community resources from the past week.


Recent Posts (February 15–21, 2026)

➡️ 1. Using AI to Analyze Business Central Snapshots, Debugs, and Profiles

📇 Author: Stefan Šošić 🗓️ Date: February 15, 2026 🌎 Link: ssosic.com 📝 Summary: Stefan demonstrates how to combine Business Central’s snapshot debugging and performance profiling with AI tools like Copilot and Claude. The approach takes call stacks, execution flows, and performance data from BC snapshots and feeds them to an AI assistant for analysis — helping developers quickly identify bottlenecks, trace unexpected behavior, and understand complex code paths without stepping through every line manually.


➡️ 2. AL Object Relations Visualizer for VS Code

📇 Author: Yun Zhu 🗓️ Date: February 19, 2026 🌎 Link: yzhums.com 📝 Summary: Yun highlights a new VS Code extension by Marko Sarić called AL Object Relations Visualizer. The extension uses D3.js force-directed graphs to visualize relationships between AL objects — with color-coded nodes for different object types, click-to-open navigation, and detailed tooltips showing object properties and connections. A useful tool for understanding and exploring complex AL project dependencies at a glance.


➡️ 3. Agents in Business Central – Part 4 – Troubleshooting

📇 Author: Bert Verbeek 🗓️ Date: February 20, 2026 🌎 Link: bertverbeek.nl 📝 Summary: Bert rounds out his agent series (for now) with a practical troubleshooting guide. The post covers the Agent Consumption page for tracking credit usage, the Agent Task Log and “View Details” pane for inspecting what the agent saw and memorized, and common pitfalls — unclear error messages, vague tooltips, too many visible fields, and duplicate captions between header and line levels that confuse the agent.


Recent Videos (February 15–21, 2026)

🎬 1. The Ultimate Claude Code Dev Container for Business Central AL Development

📺 Channel: Stefan Maron
🗓️ Date: February 19, 2026
🌎 Link: youtube.com
📝 Summary: You’ve seen what Claude Code can do for AL development — Stefan makes it easy for everyone to get started. In this stream, Stefan is building a plug-and-play dev container that ships with Claude Code, the AL tooling, and a ready-to-use AI profile. No manual setup, no guesswork. Just clone and go.


Community Resources

Official Resources

GitHub Repositories

  • microsoft/BCApps – Repository for collaboration on Microsoft Dynamics 365 Business Central applications.
  • microsoft/BCTech – Business Central technology samples.
  • microsoft/ALAppExtensions – Repository for collaboration on Microsoft AL application add-on and localization extensions for Microsoft Dynamics 365 Business Central.
  • microsoft/AL – Home of the Dynamics 365 Business Central AL Language extension for Visual Studio Code.
  • StefanMaron/MSDyn365BC.Code.History – Contains the Microsoft Business Central Code. Updated each month.

Follow on Social Media


Stay Connected

The Business Central AL development community stays active with valuable content on AL development, upgrades, integrations, and tooling improvements. Following #MSDyn365BC and #BusinessCentral on Twitter/X is a great way to catch new posts as they’re published.


Note: This review is compiled from publicly available blog posts and community resources. Links to external blog posts are provided for your information only and do not constitute endorsement or validation of their content. Publication information and availability are subject to change. Always verify information against official documentation for production use.

Permanent link to this article: https://www.dvlprlife.com/2026/02/weekly-review-business-central-al-development-february-15-21-2026/

Setting Up Azure Application Insights for Business Central

If you’ve read anything about monitoring Business Central—whether it’s tracking long-running queries, failed web service calls, or your own custom telemetry events—the advice always starts with, “Send it to Application Insights.” But if you haven’t set one up before, the Azure portal can feel a bit overwhelming. There are resource groups, workspaces, connection strings, and a few easy-to-miss settings that can trip you up.

This post walks through the entire setup from scratch: Creating the Azure resources, wiring the connection string into your AL extension’s app.json, and then trimming the noise with transformation rules so you’re only paying for the telemetry you actually need.

You can find the full code for the example on GitHub.

Azure AppInsights displaying Telemetry events

What We’re Building

By the end of this walkthrough, you’ll have:

  • A dedicated Resource Group in Azure to keep your Business Central telemetry resources organized.
  • A Log Analytics Workspace that stores the underlying log data.
  • An Application Insights resource connected to that workspace and ready to receive telemetry.
  • Your AL extension (or Business Central environment) configured to send telemetry to that resource.
  • Transformation rules in place to reduce data volume and cost.

Let’s get started.


Part 1: Creating the Azure Resources

All three resources are created in the Azure portal (portal.azure.com). You’ll need an active Azure subscription with permissions to create resources.

Step 1 — Create a Resource Group

A resource group is a logical container in Azure. Grouping your telemetry resources together makes them easy to find, manage, and clean up later.

 Azure Resource Group Creation

  1. Navigate to the Azure portal and sign in.
  2. Search for Resource groups in the top search bar and select it.
  3. Click + Create.
  4. Select your Subscription.
  5. Enter the Resource group name: BC-Telemetry.
  6. Select a Region close to your Business Central environment (e.g., East US, West Europe).
  7. Click Review + create, then Create.

That’s it—your container is ready.

Step 2 — Create a Log Analytics Workspace

Application Insights uses a Log Analytics workspace as its backing store. This is a workspace-based model that Microsoft now requires for all new Application Insights resources.

Log Analytics Workspace Creation

  1. In the Azure portal, search for Log Analytics workspaces and select it.
  2. Click + Create.
  3. Set the Subscription and Resource group to BC-Telemetry.
  4. Enter a Name (e.g., BC-Log-Analytics). The name must be globally unique.
  5. Select the same Region you used for the resource group.
  6. Click Review + create, then Create.
  7. Wait for the deployment to complete.

Log Analytics Workspace Create

The workspace is where your raw log data lives. You won’t interact with it directly very often—Application Insights provides the query and visualization layer on top—but it’s a required piece of the architecture.

Step 3 — Create an Application Insights Resource

This is the resource that actually receives telemetry from Business Central and gives you access to querying, dashboards, and alerts.

Application Insights Resource Create

  1. In the Azure portal, search for Application Insights and select it.
  2. Click + Create.
  3. Set the Subscription and Resource group to BC-Telemetry.
  4. Enter a Name (e.g., bc-telemetry-ai).
  5. Select the same Region as your other resources.
  6. Under Resource Mode, ensure Workspace-based is selected.
  7. For Log Analytics Workspace, select the bc-telemetry-law workspace you just created.
  8. Click Review + create, then Create.

Application Insights Resource Create

  1. Once deployed, open the new Application Insights resource.
  2. On the Overview page, locate the Connection String. Copy it—you’ll need this in the next section.

Application Insights Overview

The connection string looks something like:

InstrumentationKey=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;IngestionEndpoint=https://eastus-0.in.applicationinsights.azure.com/;...

Keep this value handy. It’s what connects Business Central to your Application Insights resource.


Part 2: Adding the Connection String to Your AL Extension

Once your Application Insights resource is ready, you need to tell Business Central where to send telemetry. There are two places you can configure this:

  • Environment level — An admin configures Application Insights on the Business Central environment itself (in the Business Central admin center). This captures all platform and extension telemetry for that environment.
  • Extension level — You add the connection string to your extension’s app.json. This captures telemetry scoped to your extension (specifically events emitted with TelemetryScope::ExtensionPublisher or TelemetryScope::All).

Both approaches can coexist—they serve different purposes. Environment-level telemetry is typically managed by the admin, while extension-level telemetry is managed by the extension publisher. This section focuses on the extension-level configuration.

Configuring app.json

Open your extension’s app.json file and add the applicationInsightsConnectionString property:

{
  "id": "00000000-0000-0000-0000-000000000000",
  "name": "My Extension",
  "publisher": "My Publisher",
  "version": "1.0.0.0",
  "applicationInsightsConnectionString": "InstrumentationKey=00000000-0000-0000-0000-000000000000"
}

A few things to know:

  • The property name is applicationInsightsConnectionString (not applicationInsightsKey—the older instrumentation key approach still works but connection strings are the current recommendation).
  • This setting only controls where your extension’s telemetry goes. It doesn’t affect environment-level telemetry.
  • If you also use TelemetryScope::All in your LogMessage calls, those events will be sent to both this resource and the environment’s Application Insights resource (if one is configured).
  • The connection string is embedded in the published .app file, so treat it as non-secret. Application Insights connection strings are designed to be safe to include in client-side code—they only allow ingestion, not reading data.

Configuring the Business Central Environment (Admin Center)

If you’re an admin and want to capture environment-wide telemetry (not just a single extension), you configure Application Insights in the Business Central admin center:

  1. Open the Business Central admin center.
  2. Select the environment you want to monitor.
  3. Click Define under Telemetry in the Details section.
  4. Enable Telemetry
  5. Paste the Connection String from your Application Insights resource, into the field for the Azure Application Insights Connection String.
  6. Click Save.

Admin Center Telemetry Setup

Once saved, the environment will begin sending platform telemetry—long-running queries, web service calls, report generation times, page views, and more—to your Application Insights resource.

Verifying the Connection

After publishing your extension (or saving the environment configuration), give it a few minutes. Then open your Application Insights resource in the Azure portal and navigate to Logs. Run a simple KQL query:

traces
| where timestamp > ago(1h)
| order by timestamp desc
| take 20

If telemetry is flowing, you’ll see trace records appearing. If your extension emits custom telemetry events, you can filter for those specifically:

traces
traces
| where customDimensions.eventId startswith "al"
| order by timestamp desc

Part 3: Minimizing Data with Transformation Rules

Application Insights charges based on data volume. Business Central environments—especially busy ones—can generate a lot of telemetry. Not all of it is useful for every scenario. Transformation rules let you filter, sample, or drop telemetry data before it’s stored, which directly reduces your costs.

Why Transformation Rules Matter

Without any filtering, a production Business Central environment can easily send gigabytes of telemetry per month. Much of that might be routine page views, verbose-level traces, or events from extensions you don’t need to monitor. Transformation rules let you intercept incoming telemetry and decide what to keep.

Where to Configure Transformations

Transformation rules are configured in the Log Analytics workspace (not directly in Application Insights). They use a feature called Workspace transformations that applies KQL-based rules to incoming data.

Here’s the general approach:

  1. Open the Log Analytics workspace (bc-telemetry-law) in the Azure portal.
  2. Navigate to Tables under Settings.
  3. Find the AppTraces table (this is where Application Insights traces data lands).
  4. Click the ellipsis () next to the table and select Create transformation. Configure Transformations 1
  5. Select an existing one or create a new transformation. Give the transformation a name (e.g., FilterVerboseTraces) Configure Transformations 2
  6. Select Transformation Editor
  7. Write a KQL transformation query that filters out the events you don’t want to store.
  8. Run the transformation query to validate it against recent data. Configure Transformations 3
  9. Apply the transformation
  10. Click Next and then Create to activate the transformation.

Example Transformation Rules

Drop verbose-level traces:

source
| where SeverityLevel >= 2

This keeps only Warning, Error, and Critical traces, dropping Verbose and Information level events. Adjust the severity threshold to match your needs.

Keep specific event types you need:

source
| where Properties.eventId startswith "ALDVLPR"

This keeps events with IDs starting with ALDVLPR if you’re monitoring those.

Keep only events from your extension:

source
| where Properties.extensionName == "sessionlogmessage1"

This is aggressive but useful if you’re paying for an Application Insights resource solely to monitor your own extension.

Tips for Transformation Rules

  • Start broad, then narrow. Begin by monitoring everything for a week or two, then look at your data volumes in Application Insights to identify what’s taking up the most space. Drop what you don’t need.
  • Test with a dev environment first. You don’t want to accidentally drop important production telemetry. Validate your transformation rules against a development or sandbox environment before applying them to production.
  • Combine rules. You can chain multiple where clauses in a single transformation query to apply several filters at once.
  • Monitor costs. After applying transformation rules, keep an eye on your Application Insights billing for a week to confirm the expected reduction.

Wrapping Up

Getting Application Insights connected to Business Central is a one-time setup that pays off immediately. With a resource group, Log Analytics workspace, and Application Insights resource in place, you have a centralized location for all the telemetry your environments and extensions generate. Adding the connection string to your app.json takes one line, and transformation rules give you fine-grained control over what data you actually store and pay for.

If you’re emitting custom telemetry events from your extension—and you should be—this infrastructure is what makes them visible and queryable. Start with the basics, get telemetry flowing, and then refine with transformation rules as your data volumes grow.

Learn more:

Note: The information discussed in this article is for informational and demonstration purposes only. Azure portal interfaces and Business Central admin center options may change over time. This content was written referencing Microsoft Dynamics 365 Business Central 2025 Wave 2 online and the Azure portal as of February 2026. Always test configurations in a sandbox environment first.

Permanent link to this article: https://www.dvlprlife.com/2026/02/setting-up-azure-application-insights-for-business-central/