Modern JavaScript for Zephyr

October 3, 2025

JS0/JSSugar

Why do we bother with JavaScript on microcontrollers? Because we've found it to be the best way to quickly build secure, reliable products for embedded systems. We understand that JavaScript doesn't solve every problem. Neither does C. Not even C++. But JavaScript and C are complementary. Having both frees developers to choose the right tool for each part of the product.

Diversity is an undisputed strength of the embedded universe. Product designers can choose a microcontroller that perfectly fits their needs, without paying for features their product won't use. This creates a challenge for embedded developers. All those different hardware platforms come with different software platforms. Learning a new software platform takes time. Porting existing code is tedious and error-prone.

Enter Zephyr. Zephyr is a software platform for embedded products. It supports over a dozen hardware architectures from dozens of silicon providers. It runs on nearly a thousand development boards. Zephyr is even open source with support from the Linux Foundation. Sounds great, right? The challenge of building software for that incredible diversity of embedded microcontrollers is solved. Or is it?

Something's missing. JavaScript. Zephyr is a C-only environment.

Moddable is pleased to share that we've begun the work to fix that. We're bringing the Moddable SDK, with its ECMAScript 2025 (that's the most recent JavaScript standard) conformant XS JavaScript engine, to Zephyr.

Get Started

If you just want to jump in and get started, here's what you need to know.

  • The zephyr branch of the Moddable SDK is ready and waiting.
  • There's detailed documentation on getting started.
  • You can develop on macOS and Linux.
  • Our initial focus is on hardware from STMicroelectronics. We like the $20 Nucleo F413ZH board for getting started.

At this stage, your feedback is essential in helping us shape our Zephyr support. If you are an experienced Zephyr developer, we'd particularly value your review of our work.

What's in the Zephyr Port

The port is coming along nicely. And, we're just getting started.

  • Tooling
    • Moddable SDK build using west & CMake
    • Firmware install
    • Run
    • Native debugging
  • Embedded development support
    • JavaScript debugging with xsbug over Zephyr serial IO
    • Performance profiling
    • Runtime instrumentation for real-time resource monitoring
  • Core functionality
  • Zephyr specific

You probably noticed that most of the APIs are standard, and most of those are based on ECMA-419, the ECMAScript Embedded Systems API. ECMA-419 is a vendor-neutral JavaScript API designed for efficiency on embedded systems, like those running Zephyr. Moddable has contributed to ECMA-419 since work on the standard began.

Note: We do not intend to support the original Moddable "pins" API on Zephyr, as ECMA-419 provides equivalent functionality.

What's Next in the Zephyr Port

For a small RTOS, Zephyr supports a remarkably wide range of features. That's a consequence of its support for so many different architectures and development boards. The vast majority of that can be made available to JavaScript developers. Here's what we're focusing on:

  • Runtime capabilities
  • Tooling
    • Support for testing with test262 and testmc
    • mcpack to bridge to npm

The Zephyr Device Tree

Most embedded operating systems start with C code. The header files define how software accesses the hardware. Not so with Zephyr. Instead, Zephyr begins with a Device Tree stored in *.dts file. The Device Tree uses its own file format, similar to XML, JSON, and YAML. It defines all the IO from clocks, timers, and GPIOs to Wi-Fi, sensors, and displays. The Device Tree was adapted from Linux.

Zephyr uses the Device Tree to generate extremely efficient C code. For example, all IO is accessed through static data structures created by C macros. If a project tries to use IO that doesn't exist, it won't even run - it fails to compile or link.

As powerful as the Device Tree is, it creates some challenges for a dynamic language like JavaScript. For example, JavaScript code expects to be able to discover IO capabilities at runtime by inspecting JavaScript objects. But, the capabilities depend on the development board. Fortunately, ECMA-419 has a solution for this in the opaquely named Host Provider Instance. Yes, I gave it that name. Fortunately it is more commonly known as the device global variable.

For the Zephyr port, we created a new tool, mcdevicetree, that writes the JavaScript code for the device global from the Device Tree. This works extremely well: on all other Moddable SDK ports we have to generate the host provider instance for each development board by hand.

Let's look at how the Device Tree works in JavaScript. Many Zephyr development boards have a button that is mapped to the name sw0. Here's the JavaScript code to access the button and receive notifications when it is pressed and released:

new device.button.sw0({
    onReadable() {
        trace(`sw0 state: ${this.read()}\n`);
    }
});

Couldn't be much easier. Zephyr's blinky sample in JavaScript is equally straightforward:

const led = new device.led.led0();
let state = 1;
Timer.repeat(() => {
    state = !state;
    led.write(state);
}, 1000);

The Device Tree takes care of details like the GPIO port and pin to use, whether the GPIOs are active high or low, and whether a pull-up resistor is needed.

This simplicity extends to other kinds of IO. Here's a serial echo app (Zephyr C version here):

new device.Serial.usart6({
    onReadable() {
        this.write(this.read());
    }
});

Maybe you noticed that these JavaScript examples are tiny. The corresponding examples in C require several files and dozens of lines of code. This significant code reduction is possible because bookkeeping is taken care of by JavaScript, careful API design, and an efficient implementation.

The names usart6, sw0, and led0 are from the Device Tree. You can look up the port names in the Device Tree source file like a C programmer. Or your JavaScript code can discover them. To list all the serial ports available, use the standard JavaScript for-in statement:

for (const port in device.Serial)
    trace(port, "\n");

Even better, you can display them interactively in xsbug, our JavaScript debugger, by inspecting the device global variable.

Conclusion

Embedded JavaScript is a powerful tool for building IoT products on embedded systems. Zephyr joins Moddable's support for the ESP32 family, ESP8266, nRF52, and Raspberry Pi Pico. Thanks to Zephyr's broad adoption by embedded hardware manufacturers, we expect to quickly grow the hardware choices available to Embedded JavaScript developers.

The Moddable SDK is open source. Your support can make our Zephyr port better sooner. You'll learn a lot along the way – about JavaScript, Zephyr, ECMA-419, and optimization techniques.