Library / SDK
nathankellenicki/node-poweredup avatar
nathankellenicki/node-poweredup

node-poweredup: driving LEGO hubs from Node or the browser

A Javascript module to interface with LEGO Powered Up components.

524 stars66 forksTypeScriptMIT

At a glance

What is it?
node-poweredup is a TypeScript module for talking to LEGO Powered Up hubs, motors, lights and sensors, and it runs in two places: on Node.js over Bluetooth Low Energy, and in a browser over Web Bluetooth. The package covers every hub family that speaks the protocol, from WeDo 2.0 to SPIKE Prime, and the documentation is unusually honest about where the compatibility stops.
Who is it for?
node-poweredup fits a classroom, a robotics club or a maker project where the hardware is a LEGO Powered Up hub and the code is JavaScript, because it hides the Bluetooth and the protocol behind an event and method API that reads like device control rather than packet handling.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 148 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One protocol, several generations of LEGO hardware

node-poweredup is a JavaScript module to interface with LEGO Powered Up components, and the first thing to understand is what Powered Up is. It is the successor to Power Functions, the system for adding electronics to LEGO models, and it is a family of ranges that are all compatible with each other on the wire.

The ranges listed are LEGO Education WeDo 2.0, LEGO Boost, LEGO Powered Up, LEGO Technic CONTROL+, LEGO Education SPIKE Prime, LEGO Education SPIKE Essential and LEGO Mindstorms Robot Inventor. Two other product lines also speak the Powered Up protocol even though they are not LEGO sets in that sense: the Duplo App-Controlled trains and LEGO Super Mario.

That breadth is the reason the library is worth using rather than writing against one hub. A school that has accumulated WeDo 2.0 kits, a robotics club that has a SPIKE Prime set and a parent who bought a Boost set all speak the same protocol, and one module covers them.

The library is written in TypeScript, published as node-poweredup on npm at version 10.1.0, and the licence is MIT. The author is Nathan Kellenicki, and the documentation site is at nathankellenicki.github.io/node-poweredup. The repository was last pushed on 2026-05-28, and while the GitHub repository publishes no releases, the announcement section of the README records that version 10.0.0 brought the library up to date with newer dependencies and new ES style syntax, which is the kind of change that breaks a project pinned to the old major.

The API reads like device control, not like packet handling

The entry point is a PoweredUP object, and the shape of the API is event driven, which is the right shape for Bluetooth, where the device appears asynchronously.

The sample usage in the README is the whole mental model:

javascript
import { PoweredUP } from "node-poweredup";
const poweredUP = new PoweredUP();

poweredUP.on("discover", async (hub) => { // Wait to discover a Hub
    console.log(`Discovered ${hub.name}!`);
    await hub.connect(); // Connect to the Hub
    const motorB = await hub.waitForDeviceAtPort("B"); // Make sure a motor is plugged into port B
    console.log("Connected");
});

So you subscribe to a discover event, and when a hub is found you connect to it and ask it to wait for a device on a given port. The waitForDeviceAtPort call takes a port letter, which is how the hardware is addressed, and it resolves once something is plugged in, which removes the usual race between the program starting and the device being attached.

Controlling a motor is a method call with a signed number:

javascript
    while (true) { // Repeat indefinitely
        console.log("Running motor A at speed 100 for 2 seconds");
        motorA.setPower(100); // Run a motor attached to port A for 2 seconds at maximum speed (100) then stop
        await hub.sleep(2000);
        motorA.brake();
        await hub.sleep(1000); // Do nothing for 1 second

The convention is that a positive number is one direction and a negative number is the other, and 100 is the maximum. Braking is a separate call rather than a setPower(0), and the sleep helper lives on the hub so that a script can pause in milliseconds without pulling in a timer library. The scanning itself is started explicitly:

javascript
poweredUP.scan(); // Start scanning for Hubs
console.log("Scanning for Hubs...");

The examples directory in the repository shows how far this goes: separate files for a motor, a sensor test, LEDs, a light matrix, Mario, a Duplo train, a Vernie remote, Vernie roaming, a slow train remote, and a browser example with its own HTML and CSS for the Web Bluetooth path.

Node.js and the browser, with the same module

The library communicates with the devices in two different ways depending on where it runs, and that is the feature that makes it usable in a classroom setting where the code has to run on a laptop without a server.

In Node.js it talks Bluetooth Low Energy through the noble library. The README is explicit that node-poweredup relies on the @stoprocent/noble library and that you must ensure the prerequisites documented for that project are satisfied, which on most systems means the native Bluetooth stack and the permissions to use it. Getting noble working is the part of the setup that costs people an afternoon, and it is the part the library deliberately does not handle for you.

In a browser it uses Web Bluetooth, so the page can talk to a hub directly with no server in the middle. The examples include a web_bluetooth.html file with its own stylesheet, which is the shape of a browser demo: the page asks the user to grant Bluetooth access to a device, and then the same PoweredUP object discovers and drives the hub. The type definitions in the manifest include @types/web-bluetooth, which is what makes the browser build type check.

The package itself is an ES module, with type set to module, and the manifest points the main field at a Node specific build and the types field at a Node specific declaration file, with a webpack config in the repository for the browser bundle. The two entry points come from that split, and it is why the published package is not usable from an old CommonJS project without a build step.

The installation itself is short, with Node.js 18.0 or newer required:

javascript
npm install node-poweredup --save

The compatibility table, and the one direction that does not work

Powered Up is one protocol, but the hardware is not uniformly interchangeable, and the README spends a long table on the subject.

The rule it states first is that most components and hubs are compatible with each other, with exceptions. The example given is that there is limited backwards compatibility between newer components and the WeDo 2.0 Smart Hub, while WeDo 2.0 components are fully forwards compatible with newer hubs. In other words, if you plug an old sensor into a new hub, it works. If you plug a new sensor into the oldest hub, some of it works.

The table then goes device by device, with the product code, the type, the compatibility with each of the four hub families and the sets the device ships in. The pattern in the rows is consistent with that rule. The WeDo 2.0 sensors and motors are marked yes across all four hubs. The Boost color and distance sensor and the Boost tacho motor are marked partial on the WeDo 2.0 Smart Hub and yes elsewhere. The Control+ large and extra large motors are partial on the WeDo 2.0 hub, no on the Boost Move Hub, and yes on the Powered Up and Control+ hubs.

The SPIKE Prime rows are the sharpest. The SPIKE Prime medium and large motors are partial on WeDo 2.0, no on the Boost Move Hub, and yes on the newer two, while the SPIKE Prime color and distance sensors are marked no on the WeDo 2.0 Smart Hub and no on the Boost Move Hub. The SPIKE Prime force sensor is the interesting one, since it is partial on WeDo 2.0 but yes on the Boost Move Hub, which is the kind of asymmetry that only a table can capture.

A second table covers the hubs themselves and their built-in features, which the library exposes directly, and the note above it says that the hubs have certain built-in features which this library exposes. That is worth a separate look if your project uses the hub's own leds or speaker rather than a peripheral.

The package, the build and the toolchain

The manifest is small and tells you what the library needs. The runtime dependencies are @stoprocent/noble for the Bluetooth transport, compare-versions for version comparison, and debug for the logging namespace, so the library does not pull in a logging framework of its own.

The build is TypeScript, with the build script running tsc in build mode, a clean script that removes the dist directory with rimraf, and a prepare and prepublishOnly script that both run the build, which is the standard way to make sure the published artefact matches the source. Documentation is generated with typedoc, and there is a typedoc.json in the repository to configure it, so the API documentation is generated from the same source as the type declarations rather than written by hand.

The development dependencies are the TypeScript toolchain, ts-node for running TypeScript directly, the type packages for node and web-bluetooth, rimraf, and typedoc itself. The repository has a .drone.yml, which is the configuration for Drone, the continuous integration system, and the presence of a .gitmodules file means there is at least one submodule in the tree.

The result of all of this is a package with a generated API site, generated type declarations and a small runtime dependency footprint, which is the right shape for a library that is likely to be installed on a Raspberry Pi or a school laptop as well as on a developer machine.

The one thing the repository does not do is publish tagged GitHub releases, so the version history lives on npm, and the README points at the npm package for the current version. For a project that depends on this, that means tracking the npm version rather than the repository tags.

Editorial conclusion

node-poweredup fits a classroom, a robotics club or a maker project where the hardware is a LEGO Powered Up hub and the code is JavaScript, because it hides the Bluetooth and the protocol behind an event and method API that reads like device control rather than packet handling. It is the wrong tool if your hardware is the older Power Functions system, since Powered Up is its successor and the two are not interchangeable here, and it is the wrong tool if the hub you own is a WeDo 2.0 Smart Hub and your peripherals are new, because the backwards compatibility runs in one direction only. Before you start, check the compatibility table for the exact hub and device pairing, install the noble prerequisites the README points to, and confirm your Node version is 18 or newer, since the module is published as an ES module and will not load in an old CommonJS setup without a build step.

Frequently asked questions

What is node-poweredup and which LEGO hardware does it support?

It is a JavaScript and TypeScript module for communicating with and controlling LEGO Powered Up devices. The protocol covers LEGO Education WeDo 2.0, LEGO Boost, LEGO Powered Up, LEGO Technic CONTROL+, LEGO Education SPIKE Prime, LEGO Education SPIKE Essential and LEGO Mindstorms Robot Inventor, and the Duplo App-Controlled trains and LEGO Super Mario also speak it.

How do I install node-poweredup and what does it need?

Node.js 18.0 or newer is required, and the install is npm install node-poweredup --save. The module relies on the @stoprocent/noble library, so the prerequisites documented for that project have to be satisfied, which is where the native Bluetooth setup lives. In a browser the same module works over Web Bluetooth.

How do I control a motor with node-poweredup?

You connect to the hub, wait for the device on a port with waitForDeviceAtPort, and then call setPower on it with a signed value, where 100 is the maximum speed and a negative value reverses the direction. Braking is a separate brake call, and hub.sleep takes a duration in milliseconds.

Are new LEGO devices compatible with older hubs in node-poweredup?

Only partly. The documentation says there is limited backwards compatibility between newer components and the WeDo 2.0 Smart Hub, while WeDo 2.0 components are fully forwards compatible with newer hubs. The compatibility table marks the Boost and SPIKE Prime devices as partial or unsupported on the WeDo 2.0 Smart Hub and on the Boost Move Hub.

Official sources

  1. Issues
  2. License: MIT
  3. nathankellenicki/node-poweredup on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nathankellenicki-node-poweredup.svg)](https://hysenlabs.com/projects/nathankellenicki-node-poweredup)

Was this useful?