How to use JavaScript Modules in AL

JavaScript can be distributed as a Module, but a Business Central ControlAddIn does not have direct support for modules. Check the video for an easy solution to this challenge!

https://youtu.be/CqJX3CihmD4

In this video, Erik demonstrates how to use JavaScript modules in AL for Business Central. The core problem: Business Central’s control add-in framework doesn’t natively support ES modules (i.e., <script type="module">), but many modern JavaScript libraries are packaged as modules. Erik walks through a clever workaround — declaring the module file as an image resource and dynamically importing it — using the open-source Deep Chat library as a practical example.

The Problem: JavaScript Modules in Business Central

JavaScript has two main flavors when it comes to how scripts are loaded:

  • Classic scripts — everything lives in the global scope. You define something, and it’s available everywhere.
  • ES Modules — a more organized approach where you explicitly import and export functionality between files.

Business Central’s control add-in infrastructure supports the first flavor just fine. You list your .js files in the Scripts property of the control add-in definition and they’re loaded as classic scripts. But if a JavaScript library uses ES module syntax — export, import, type="module" — you’ll immediately run into errors like:

“Unexpected token export” and “Failed to execute write on document: unexpected token export.”

There is no type="module" attribute you can set in the control add-in manifest. So how do you include a modern JavaScript library that’s packaged as a module?

Setting Up the Project

Erik starts with a fresh AL app and walks through building the control add-in from scratch. The example uses Deep Chat, an open-source, MIT-licensed chat UI component — perfect for building an agent-style chat experience inside Business Central.

Step 1: Download the JavaScript Bundle

Rather than referencing an external CDN URL, Erik downloads the Deep Chat JavaScript bundle (deepChat.js) directly into the project. This gives full control over the version being used and avoids external dependencies at runtime.

Step 2: Create the Control Add-in

A control add-in definition in AL has three key sections: scripts (and resources), properties, and an interface. Here’s the control add-in definition:

controladdin DeepChat
{
    Scripts =
        'deepchat/script.js',
        'deepchat/startup.js';

    Images =
        'deepchat/deepChat.js';

    VerticalStretch = true;
    HorizontalStretch = true;

    event ControlReady();
    procedure Init();
}

Notice a few important things:

  • Scripts — includes two classic scripts: script.js (which contains the Init function) and startup.js (which fires the ControlReady event).
  • Images — this is where the trick happens. The Deep Chat module (deepChat.js) is declared as an image. It’s obviously not an image, but the Images property is really just a general-purpose resource bucket. Business Central doesn’t care about the file type — it just makes the file available as a resource.
  • ControlReady — every control add-in should fire this event so the AL code knows when it’s safe to call into the control. If you call into the control before it’s ready, things simply won’t work.
  • Init — a procedure the AL side can call to initialize the chat UI.

Step 3: Create the Host Page

The page uses the UserControlHost page type, which is purpose-built for hosting control add-ins:

page 50100 "Deep Chat Test"
{
    PageType = UserControlHost;
    Caption = 'Deep Chat Demo';
    ApplicationArea = all;

    layout
    {
        area(Content)
        {
            usercontrol(deepchat; DeepChat)
            {
                trigger ControlReady()
                begin
                    CurrPage.deepchat.Init();
                end;
            }
        }
    }
}

The flow is straightforward: when the control signals it’s ready, the page calls Init() to set up the chat interface.

Step 4: The Startup Script

The startup script is executed when the control first loads. Its job is to signal back to AL that the control is ready:

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

InvokeExtensibilityMethod is the bridge from JavaScript back to AL. The first parameter is the event name, and the second is an array of parameter values (empty in this case).

Step 5: The Init Script — And the Module Import Trick

Here’s where the magic happens. In script.js, the Init function needs to place the Deep Chat component on the page. Control add-ins render inside an iframe, and Microsoft places a <div> with the ID controlAddIn inside that iframe:

function Init() {
    var container = document.getElementById('controlAddIn');
    container.innerHTML = `<deep-chat demo="true"></deep-chat>`;
}

But if you try to load deepChat.js as a regular script (via the Scripts property), you’ll get the “unexpected token export” error because it’s an ES module.

The Workaround: Dynamic Import via Image Resource

Since we declared deepChat.js in the Images property, we can retrieve its URL at runtime using Microsoft.Dynamics.NAV.GetImageResource. Then we use a dynamic import() — which is the standard JavaScript way to load a module at runtime — to bring it in:

import(Microsoft.Dynamics.NAV.GetImageResource('deepchat/deepChat.js'));

This line goes in the startup.js file, before the ControlReady call, so the module is loaded before the AL side tries to use its components. The key insight is:

  1. The Images property makes the file available as a resource with a URL.
  2. GetImageResource returns that URL at runtime.
  3. import() performs a dynamic ES module import from that URL.

The browser treats the dynamic import as a proper module load — with full export/import support — bypassing the limitation that the Scripts property only loads classic scripts.

The complete startup.js looks like this:

import(Microsoft.Dynamics.NAV.GetImageResource('deepchat/deepChat.js'));

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

After deploying and refreshing, the Deep Chat component renders successfully inside Business Central — a fully functional chat interface loaded from a JavaScript module.

Summary

Here’s the complete recipe for using JavaScript modules in a Business Central control add-in:

  1. Declare your module file in the Images property of the control add-in, not in Scripts. The Images property is really just a resource bucket — it doesn’t care about file types.
  2. Use import(Microsoft.Dynamics.NAV.GetImageResource('path/to/module.js')) in your startup script to dynamically load the module at runtime.
  3. Use your classic scripts normally for your own Init functions, event wiring, and other non-module code.

Erik notes that ideally Microsoft should extend the control add-in definition to allow marking a script as a module directly. But until that happens, this approach is a clean, workable hack that lets you use any modern JavaScript library — modules and all — inside Business Central.