Video: Add dynamically HTML rendering to Business Central

In this video, I’ll show a way to dynamically add HTML content to a Business Central page.

This can be used to personalize your apps or to integrate controls and visualizations that are not possible with normal AL alone.


In this video, Erik demonstrates how to render dynamic HTML directly inside a Business Central page using control add-ins. This technique allows you to create custom visualizations, build richer user interfaces, and give your pages a more polished, personal feel — going beyond what’s possible with standard AL page controls. Erik walks through the entire process from scratch: creating the control add-in definition, writing the JavaScript to inject HTML into the page’s DOM, and wiring it all together with AL code that generates an HTML table populated with G/L Account data.

Why Render HTML in Business Central?

Business Central’s standard page controls are functional but limited in terms of visual customization. By injecting HTML directly into a page via a control add-in, you can:

  • Create custom UI controls that don’t exist natively in Business Central
  • Add data visualizations, charts, or formatted displays
  • Build more visually appealing registration screens or dashboards
  • Give your app pages a distinctive, branded look and feel

Erik notes that this is a technique they use in some of their own apps to make certain screens more user-friendly and visually polished.

The Architecture: How It Works

A user control (control add-in) in Business Central is hosted inside an iframe — essentially a web page within a web page. This iframe contains a <div> element that serves as the container for your custom content. The approach involves three key pieces:

  1. A control add-in definition (AL) — declares the interface between AL and JavaScript
  2. A startup script (JavaScript) — initializes the control and signals readiness back to AL
  3. A scripts file (JavaScript) — contains the functions that AL can call to manipulate the DOM

Step 1: Define the Control Add-in

The control add-in is defined as an AL object of type controladdin. Note that this object type doesn’t have a number — which Erik points out is “very exotic.” It declares the startup script, any additional script libraries, layout properties, and the interface contract (events and procedures) between AL and JavaScript.

controladdin HTML
{
    StartupScript = 'startup.js';
    Scripts = 'scripts.js';
    HorizontalStretch = true;
    VerticalStretch = true;
    RequestedHeight = 400;
    event ControlReady();
    procedure Render(HTML: Text);
}

Key things to note:

  • StartupScript runs when the control add-in loads — used for initialization
  • Scripts contains your library functions that AL can invoke
  • HorizontalStretch and VerticalStretch allow the control to fill available space
  • RequestedHeight sets an initial height of 400 pixels
  • ControlReady() is an event that fires from JavaScript back to AL, signaling the control is loaded
  • Render(HTML: Text) is a procedure that AL calls to pass HTML into JavaScript

Step 2: The Startup Script

The startup script runs when the iframe loads. Its job is to grab a reference to the container <div> inside the iframe and then notify the AL runtime that the control is ready to receive commands.

HTMLContainer = document.getElementById("controlAddIn");

Microsoft.Dynamics.NAV.InvokeExtensibilityMethod("ControlReady",[]);

There are two critical things happening here:

  1. Getting the container: The iframe that hosts a control add-in contains a <div> with the ID controlAddIn. By grabbing a reference to this element, we have a place to inject our HTML. Spelling is very important here — it must match exactly.
  2. Signaling readiness: Microsoft.Dynamics.NAV.InvokeExtensibilityMethod is how JavaScript communicates back to AL. It invokes the ControlReady event, passing an empty parameter array. Without this signal, AL wouldn’t know when it’s safe to start calling JavaScript functions.

Erik discovered a gotcha during the demo: if you misspell InvokeExtensibilityMethod (he initially typed “Encode” instead of “Invoke”), the JavaScript fails silently. The AL debugger won’t show any error — you have to open the browser’s developer tools (F12) to see the JavaScript error. This is an important debugging tip to keep in mind.

Step 3: The Render Function

The scripts file contains the Render function that corresponds to the procedure Render(HTML: Text) declared in the control add-in. This function takes the HTML string passed from AL and injects it into the DOM.

function Render(html)
{
    HTMLContainer.insertAdjacentHTML('beforeend',html);
}

The insertAdjacentHTML method is a standard DOM API that inserts HTML at a specified position relative to an element. The 'beforeend' position means the HTML is inserted just inside the container element, after its last child — effectively appending content to the container.

Step 4: The Business Central Page

With the JavaScript infrastructure in place, the AL page ties everything together. The page uses the usercontrol keyword to embed the control add-in, and then responds to the ControlReady trigger to start rendering content.

page 50100 "Dynamic HTML Rendering"
{
    Caption = 'Dynamics HTML Rendering';
    UsageCategory = Administration;
    ApplicationArea = all;

    layout
    {
        area(Content)
        {
            usercontrol(html; HTML)
            {
                ApplicationArea = all;
                trigger ControlReady()
                begin
                    //CurrPage.html.Render('<a href="https://www.hougaard.com">A great blog!</a>');
                    CurrPage.html.Render(CreateTable(10, 8));
                end;
            }
        }
    }
    procedure CreateTable(rows: Integer; Columns: Integer): Text
    var
        GL: Record "G/L Account";
        out: Text;
        r, c : Integer;
    begin
        GL.FindSet();
        out := '<table border="1" style="width: 100%;">';
        for r := 1 to rows do begin
            out += '<tr>';
            for c := 1 to Columns do begin
                if (c mod 5) = 0 then
                    out += '<td style="background-color:tomato;">' + GL.Name + '</td>'
                else
                    out += '<td style="background-color:powderblue;">' + GL.Name + '</td>';

                GL.Next();
            end;
            out += '</tr>';
        end;
        out += '</table>';
        exit(out);
    end;
}

The Simple Example: A Hyperlink

Erik first demonstrates the simplest possible case — rendering a single anchor tag. The commented-out line shows this:

CurrPage.html.Render('<a href="https://www.hougaard.com">A great blog!</a>');

Because the control add-in lives inside an iframe, clicking the link actually navigates the iframe to the target URL — effectively embedding a website inside Business Central. While Erik jokes that he “recommends everybody” embed his blog in Business Central, this does illustrate an important architectural point: the iframe provides a sandboxed environment for your HTML content.

The Real Example: A Dynamic Data Table

The CreateTable procedure is where things get interesting. It builds an HTML table string populated with actual Business Central data from the G/L Account table:

  • It opens a <table> tag with a border and 100% width (which Erik humorously calls the “1998 rendering style”)
  • It loops through the requested number of rows and columns
  • For each cell, it reads the current G/L Account’s Name and advances to the next record
  • Every 5th column gets a tomato background color, while the others get powderblue
  • The complete HTML string is returned and passed to the JavaScript Render function

The result is a colorful 10×8 grid of G/L Account names rendered directly in the Business Central page — something that would be impossible with standard AL page controls.

Debugging Tips

Erik ran into a JavaScript error during the live demo that highlights an important debugging workflow:

  • The AL debugger won’t catch JavaScript errors. When the startup script failed due to a typo, the AL side was a “happy camper” — no errors, no warnings, just a blank control.
  • Use the browser’s developer tools (F12) to inspect the console for JavaScript errors. In this case, the console clearly showed that Microsoft.Dynamics.NAV.EncodeExtensibilityMethod was not a function — pointing directly to the typo.
  • Spelling matters in JavaScript. The element ID controlAddIn and the method name InvokeExtensibilityMethod must be exact.

Safety and Scope

Erik emphasizes that this technique is “fairly safe” because you’re working within the confinement of the iframe. Your HTML and JavaScript execute in a sandboxed environment — you can’t accidentally break the Business Central UI outside of your control add-in’s area. As Erik puts it: “If you can do it in HTML and it can fit on the screen, you can fit it in here.”

Going Further

Erik mentions a few additional capabilities that build on this foundation:

  • You can embed images and other resources inside the control add-in and access them by name
  • You can create sophisticated custom controls and visualizations using any JavaScript library
  • You can use CSS for modern, polished styling instead of the retro border="1" approach
  • Events can pass parameters back from JavaScript to AL, enabling two-way communication (e.g., click handlers)

Summary

Dynamic HTML rendering in Business Central via control add-ins is a powerful technique that opens up a world of UI possibilities. The pattern is straightforward: define a controladdin with events and procedures, write startup JavaScript to grab the container and signal readiness, implement render functions that inject HTML into the DOM, and call those functions from AL with dynamically generated HTML content. While it requires stepping outside the comfort zone of pure AL development into JavaScript territory, the payoff is the ability to create truly custom, visually rich experiences inside Business Central pages.