How add the BrightScript Engine to a Web Application

July 25, 2026 · View on GitHub

The brs-engine library is published as an NPM package, so you can install it as a dependency in your project with:

$ npm install brs-engine

Sample Application

This repository provides a sample web application for testing the engine, located under the packages/browser/ folder, you can download the full example from the release page with the libraries already integrated, or you can try the simpler example listed below.

To learn more about the methods and events exposed by the library visit the API documentation.

SceneGraph extension or Draw 2D only?

There are two typical integration profiles:

  • Draw 2D (roScreen) apps and games – If your channel only uses the classic 2D APIs (roScreen, roBitmap, roCompositor, etc.) you only need to deploy lib/brs.api.js, lib/brs.worker.js and assets/common.zip. Nothing else is required because the interpreter ships those artifacts in the core bundle.
  • SceneGraph apps (roSGScreen) – If the package contains pkg:/components/ XML files or you otherwise rely on SceneGraph nodes, bundle the brs-scenegraph extension next to the worker (lib/brs-sg.js). The packaging step automatically asks the worker to load it whenever components are detected, so keeping the file available is all that is needed. For an encrypted .bpk the component files are folded into the encrypted blob and removed from the package, so a components/ directory marker is preserved and used to detect the SceneGraph app instead.

The additional bundle is only needed when SceneGraph is in play, which keeps Draw 2D deployments lean while allowing full SceneGraph parity when required.

Configuring extensions via DeviceInfo

Hosts now decide which extensions are available to the interpreter by setting the optional extensions map on the DeviceInfo object passed to brs.initialize. Each entry pairs a SupportedExtension enum value with the module path string the worker can load (e.g., "./brs-sg.js"). The packaging layer only injects an extension into the worker payload when:

  1. The app bundle needs it (e.g., contains pkg:/components/ files, or — for an encrypted .bpk — a components/ directory marker).
  2. The extension exists in the DeviceInfo.extensions map.

This makes it explicit which add-ons your deployment supports and prevents the worker from fetching bundles that the host has not approved.

import * as brs from "brs-engine";

const deviceOverrides: Partial<brs.DeviceInfo> = {
    extensions: new Map([[brs.SupportedExtension.SceneGraph, "./brs-sg.js"]]),
};

await brs.initialize(deviceOverrides, { debugToConsole: true });

If you decide to ship additional extensions (SDK1, BrightSign, etc.) just register them in the same map with the corresponding module path.

Important Notes:

Your web application cannot be executed as pure HTML page, because some functionalities used by the engine have security restrictions on the browser platform, so you will need a web server to run it. For that you can use Apache, IIS or any other simpler web server, but please make sure that your web application is hosted with COOP and COEP custom headers to allow isolation and enable the browser to support ShareArrayBuffer. More information visit: Chrome - Enabling ShareArrayBuffer

Using Webpack DevServer

The repository provides a script to run the Webpack DevServer, with COOP and COEP headers, to help testing the web application on development environment. In order to start the web server, execute:

$ npm run start

The command above, will open a new tab in the default browser, directly on the address: http://localhost:6502/. It may take a few seconds to the web page to show up, as the script will load the app and the API library first.

By default the server will use the port 6502, if you prefer another port just change it inside the webpack configuration file at packages/browser/config/webpack.config.js.

Simple Web Example

Here's the most simple example to use the app to run BrightScript code. Make sure the library files brs.api.js and brs.worker.js are located in a folder name lib/ under the main folder where you host the file example.html (code below). Remember that you need to host this files on a web server like the one described in the section above.

example.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>BrightScript Engine Example</title>
    <link rel="icon" href="data:;base64,iVBORwOKGO=" />
</head>
<body>
    <canvas id="display" width="854px" height="480px"></canvas>
    <video id="player" style="display: none" crossorigin="anonymous"></video><br /><br />
    <label for="source-code">
        Type some BrightScript code: (open Developer Tools console to see <b>print</b> outputs)
    </label><br /><br />
    <textarea id="source-code" name="source-code" rows="15" cols="100">
' BrightScript Hello World
sub main()
    text = "Hello World"
    purple=&h6F1AB1FF
    white = &hFFFFFFFF
    screen = createObject("roScreen")
    screen.clear(purple)
    font = createObject("roFontRegistry").getDefaultFont()
    w = font.getOneLineWidth(text, screen.getWidth())
    h = font.getOneLineHeight()
    x = cInt((screen.getWidth() - w) / 2)
    y = cInt((screen.getHeight() - h) / 2)
    screen.drawText(text, x, y, white, font)
    print text
    screen.swapBuffers()
end sub
    </textarea><br />
    <input id="clickMe" type="button" value="Run Code!" onclick="executeBrs();" />
    <script type="text/javascript" src="lib/brs.api.js"></script>
    <script type="text/javascript">
        globalThis.addEventListener("load", main, false);
        async function main() {
            // Subscribe to Events (optional)
            brs.subscribe("myApp", (event, data) => {
                if (event === "loaded") {
                    console.info(`Source code loaded: ${data.id}`);
                } else if (event === "started") {
                    console.info(`Source code executing: ${data.id}`);
                } else if (event === "closed" || event === "error") {
                    console.info(`Execution terminated! ${event}: ${data}`);
                }
            });
            // Initialize Simulated Device
            await brs.initialize({}, { debugToConsole: true, disableKeys: true });
        }
        // OnClick handler to execute the code
        function executeBrs() {
            source = document.getElementById("source-code").value;
            brs.execute("main.brs", source, { clearDisplayOnExit: false });
        }
    </script>
</body>
</html>

How to Debug your BrightScript Code

You can see the debug messages from print statements in your code using the browser or desktop application console, just make sure you open the Developer Tools (Ctrl+Shift+i) before loading your app .zip package or .brs file. Exceptions from the engine library will be shown there too.

If you added a break point (stop) in your code, you can also debug using the browser console, just send the commands using debug method like this: brs.debug("help"), but for a better debugging experience, is recommended to use the desktop application integrated with either:

The Roku registry data is stored on the browser Local Storage and you can inspect it also using the Developer Tools (Application tab).

If your code does show an error in some scenario not listed on the limitations documentation, feel free to open an issue.

Games and Demos

You can try the engine by running one of the demonstration apps included in the repository, those are pre-configured as clickable icons on packages/browser/index.html and packages/browser/index.js. In addition to those, you can load your own code, either as a single .brs file or an app .zip/.bpk package. Below there is a list of tested games that are publicly available with source code, download the .zip files and have fun!