Skip to main content
This page documents the contract between the AL side (the hosting page) and the React side (the control add-in). Treat it as the source of truth when changing either layer.

How a control add-in works

  1. The AL page declares a usercontrol(Name; "Control Add-in Name") field in its layout.
  2. The Control Add-in object (controls/RosterGridHAC.al) declares the JS files to load, the event names it raises (calls from AL into JS), and the procedures it exposes (calls from JS into AL).
  3. BC injects an iframe sourcing the bundle’s HTML wrapper. The React app boots inside.
  4. The control’s Scripts property points at /scripts/assets/js/base.js — the Vite output.

The event contract

Outbound (React → AL)

Every call is Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(eventName, [parameters]). The AL trigger with the matching name runs, reads data from BC tables, builds a JsonArray or JsonObject, and calls a control-add-in procedure (back into JS) with the result. Gantt-specific events (GetProjects, CheckIfUserCanEdit, NewTask, CreateTaskPlanningLines, etc.) are also declared and fire when that screen is re-mounted.

Inbound (AL → React)

The control add-in declares procedures, which BC compiles into JavaScript callbacks that the AL page calls with CurrPage.Control.<ProcedureName>(<Args>). The React app registers each as a window.<NAME_IN_UPPER_SNAKE> function in dataService.ts.

The startup handshake

If anything in steps 4-7 fails, the iframe stays blank. Debug by opening browser DevTools while inside BC, set a breakpoint in startup.js, and watch the console.

Working with the bridge in code

The 120-second timeout protects against AL never calling back.

Bundle layout (after npm run build)

The Control Add-in’s Scripts and StyleSheets properties name js/base.js and app.css verbatim. Never rename them.

Updating the contract

When you add a new feature that needs a new event or procedure:
  1. Add the event/procedure to controls/RosterGridHAC.al.
  2. Implement the AL trigger on the hosting page (Pag70003175).
  3. Add the matching service function in dataService.ts.
  4. Rebuild the React bundle (npm run build).
  5. Rebuild the AL .app.
  6. Republish.
Skipping the React rebuild is the single most common cause of “feature works in dev but not in BC.”

Why no direct OData or fetch from React

Permission uniformity (AL triggers run under the BC user’s permission set), audit (every BC table write goes through the AL trigger and into BC’s change log), multi-environment support (the iframe inherits the BC session), and no CORS/auth issues — the iframe and the AL page share an origin within BC’s web shell.