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:
- A control add-in definition (AL) — declares the interface between AL and JavaScript
- A startup script (JavaScript) — initializes the control and signals readiness back to AL
- 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:
StartupScriptruns when the control add-in loads — used for initializationScriptscontains your library functions that AL can invokeHorizontalStretchandVerticalStretchallow the control to fill available spaceRequestedHeightsets an initial height of 400 pixelsControlReady()is an event that fires from JavaScript back to AL, signaling the control is loadedRender(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:
- Getting the container: The iframe that hosts a control add-in contains a
<div>with the IDcontrolAddIn. By grabbing a reference to this element, we have a place to inject our HTML. Spelling is very important here — it must match exactly. - Signaling readiness:
Microsoft.Dynamics.NAV.InvokeExtensibilityMethodis how JavaScript communicates back to AL. It invokes theControlReadyevent, 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
tomatobackground color, while the others getpowderblue - The complete HTML string is returned and passed to the JavaScript
Renderfunction
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.EncodeExtensibilityMethodwas not a function — pointing directly to the typo. - Spelling matters in JavaScript. The element ID
controlAddInand the method nameInvokeExtensibilityMethodmust 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.