In this video I’m showing how to interface with a barcode scanner from the web client in Business Central.

In this video, Erik walks through the process of interfacing a barcode scanner with Business Central using JavaScript control add-ins. The challenge: barcode scanners send raw keystrokes to the browser, including control characters like Ctrl+J that can trigger unwanted browser actions (like opening the downloads panel). Erik demonstrates how to intercept these keystrokes, filter out the noise, and cleanly pass scanned barcode data into Business Central — all with a $15 barcode scanner from AliExpress.
The Problem: Barcode Scanners and Unwanted Keystrokes
The story begins with a question from a Facebook follower: “Erik, I’ve got trouble interfacing with a barcode scanner from Business Central — it keeps sending weird character key presses and opening up stuff that we don’t want to see.” This is a common issue. Barcode scanners typically emulate a keyboard, sending a rapid stream of keystrokes followed by control characters like carriage return and Ctrl+J. In a web browser, Ctrl+J opens the downloads panel — clearly not what you want when scanning items in a warehouse.
When Erik scans a barcode while focused on Business Central, the downloads panel pops open because the scanner is sending a Ctrl+J keystroke at the end of the barcode data. The core challenge is: how do you intercept and control this stream of key presses within Business Central?
Why AL Alone Isn’t Enough
AL, the programming language for Business Central extensions, doesn’t provide any keyboard control. You have triggers like OnValidate, but there’s no mechanism to intercept or suppress raw keystrokes. To solve this problem, you need to turn to JavaScript via a control add-in.
Setting Up the Control Add-In
A control add-in in Business Central allows you to embed custom JavaScript within a page. Erik starts by creating the scaffolding: a simple table to store scanned data, a page to display it, and the control add-in definition.
The control add-in is defined as a tiny 1×1 pixel iframe — it doesn’t need to be visible, it just needs to exist on the page so the JavaScript can run:
controladdin ScannerInterface
{
RequestedHeight = 1;
RequestedWidth = 1;
Scripts = 'scanner.js';
event Scanned(Barcode: Text);
}
On the page side, the control add-in is added as a user control, and the Scanned event trigger handles storing the barcode data:
usercontrol(Scanner; ScannerInterface)
{
ApplicationArea = All;
Visible = true;
trigger Scanned(Barcode: Text)
var
ScanRec: Record "Scan Data";
begin
ScanRec.Init();
ScanRec.Data := Barcode;
ScanRec.Insert(true);
CurrPage.Update(false);
end;
}
Intercepting Keystrokes with JavaScript
Here’s where it gets interesting. The JavaScript file scanner.js needs to listen for keyboard events. However, there’s an important subtlety: since the control add-in lives inside a 1×1 pixel iframe, that iframe will never receive keyboard focus. You need to break out of the iframe and attach the event listener to the parent window — the Business Central page itself:
window.parent.addEventListener('keydown', (event) => {
console.log(event);
});
As Erik notes, “Because JavaScript has no security model at all, I can just go window.parent — now we’re breaking out of the iframe into Business Central.”
Building a Character Buffer
Since the keydown event fires once per character, you need a buffer to accumulate the characters as they arrive:
var buffer = '';
window.parent.addEventListener('keydown', (event) => {
if (event.keyCode >= 48) {
buffer = buffer + String.fromCharCode(event.keyCode);
console.log(buffer);
}
});
Key code 48 corresponds to the character ‘0’, so this filter ensures only printable characters are added to the buffer.
Handling Control Characters (Ctrl+J)
The barcode scanner sends a carriage return (key code 13) followed by Ctrl+J (key code 74) at the end of each scan. These need to be intercepted and suppressed to prevent the browser from acting on them. Erik uses event.preventDefault() and event.stopPropagation() to kill these events:
var buffer = '';
var dropUntil74 = false;
window.parent.addEventListener('keydown', (event) => {
if (dropUntil74) {
if (event.keyCode === 74) {
dropUntil74 = false;
// Send the buffer to Business Central
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('Scanned', [buffer]);
buffer = '';
}
event.preventDefault();
event.stopPropagation();
} else if (event.keyCode === 13) {
dropUntil74 = true;
event.preventDefault();
event.stopPropagation();
} else if (event.keyCode >= 48) {
buffer = buffer + String.fromCharCode(event.keyCode);
}
});
The flow works like this:
- Characters arrive rapidly and get added to the buffer
- When a carriage return (13) is detected, the code enters “drop mode”
- All subsequent keystrokes are suppressed until key code 74 (J) arrives
- When the J arrives, the buffer is sent to Business Central via
InvokeExtensibilityMethod - The buffer is cleared and normal operation resumes
Distinguishing Scanning from Typing
There’s still a problem: if a user types something on the keyboard, those keystrokes also get added to the buffer. Since the scanner is essentially just another keyboard, how do you tell the difference?
Erik’s clever solution: timing. A barcode scanner sends characters far faster than any human can type. By measuring the time between keystrokes, you can distinguish scanner input from manual typing:
var buffer = '';
var dropUntil74 = false;
var lastDigit = new Date().getTime();
window.parent.addEventListener('keydown', (event) => {
var newDigit = new Date().getTime();
if (dropUntil74) {
if (newDigit - lastDigit > 200) {
// Too much time has passed — reset drop state
dropUntil74 = false;
} else if (event.keyCode === 74) {
dropUntil74 = false;
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('Scanned', [buffer]);
buffer = '';
}
event.preventDefault();
event.stopPropagation();
} else if (event.keyCode === 13 && (newDigit - lastDigit < 100)) {
dropUntil74 = true;
event.preventDefault();
event.stopPropagation();
} else if (event.keyCode >= 48) {
if (newDigit - lastDigit < 100) {
// Fast enough to be a scanner
buffer = buffer + String.fromCharCode(event.keyCode);
} else {
// Too slow — reset buffer, this is manual typing
buffer = String.fromCharCode(event.keyCode);
}
}
lastDigit = newDigit;
});
The magic number here is 100 milliseconds. If less than 100ms pass between keystrokes, it's almost certainly a scanner. If more time passes, the buffer resets — it's just a human typing. This effectively filters out manual keyboard input from being treated as barcode data.
Erik demonstrates this working: he types "555" on the keyboard, then scans — only the barcode data appears. He types "777", scans again — still just the barcode. The timing filter cleanly separates the two input sources.
Safety Reset for the Drop State
There's one more edge case to handle: if the drop state gets activated but the expected Ctrl+J never arrives (or arrives too late), the keyboard would effectively become locked. Erik adds a timeout check within the drop state — if more than 200ms pass, the drop state is cancelled and normal keyboard operation resumes.
The Provided Source Code
The source code included with this post shows a slightly different (and more streamlined) approach using the built-in BarcodeScannerProviderAddIn from the System.Device namespace:
namespace DefaultPublisher.BarcodeAddIn;
using Microsoft.Sales.Customer;
using System.Device;
pageextension 50100 CustomerListExt extends "Customer List"
{
layout
{
addfirst(content)
{
usercontrol(BarCode; BarcodeScannerProviderAddIn)
{
ApplicationArea = all;
Visible = true;
trigger BarcodeReceived(Barcode: Text; Format: Text)
begin
message(Barcode);
end;
}
}
}
trigger OnOpenPage();
begin
Message('App published: Hello world');
end;
}
This demonstrates how Business Central's built-in barcode scanner provider add-in can be used as an alternative, providing a BarcodeReceived trigger that delivers both the barcode value and its format directly, without needing custom JavaScript keystroke handling.
Key Takeaways
- Barcode scanners are keyboard emulators — they send raw keystrokes including control characters that can trigger unwanted browser behavior
- AL doesn't have keyboard control — you need JavaScript via control add-ins to intercept and manage keystrokes
- Break out of the iframe — use
window.parentto attach event listeners to the Business Central page since the control add-in's iframe won't receive focus - Suppress control characters — use
preventDefault()andstopPropagation()to stop unwanted keys from reaching the browser - Use timing to filter input — scanners send characters much faster than humans type; a 100ms threshold effectively distinguishes scanner input from keyboard input
- Handle edge cases — add timeout resets for the drop state to prevent keyboard lockups
Conclusion
With a relatively small amount of JavaScript, you can build a robust barcode scanner interface for Business Central that intercepts raw keystrokes, suppresses problematic control characters, and intelligently distinguishes scanner input from manual typing using timing analysis. The result is clean barcode data delivered through an AL event trigger, ready to be processed by your business logic. Erik mentions a planned follow-up video covering how to integrate this scanner functionality into practical scenarios like sales orders — where you'd need to ensure scanned data goes into the right field rather than accidentally populating quantity or price fields with barcode digits.