gamepad-node
v1.1.0
Published
Browser Gamepad API implementation for Node.js with native SDL2 bindings
Downloads
43
Maintainers
Readme
gamepad-node
W3C Gamepad API for Node.js using SDL2. Works exactly like the browser API, but better - every controller gets mapping: "standard", not just the handful browsers recognize.
Features
- Browser-compatible API -
navigator.getGamepads()works exactly like in browsers - Every controller gets
mapping: "standard"- not just the 20-30 browsers recognize - Positional button mapping - buttons mapped by physical position (N/S/E/W), not labels (A/B/X/Y)
- 2100+ controllers via SDL2's community database
- 321 more via EmulationStation configs (Knulli + Batocera - position-aware!)
- Generic fallback for everything else
- Hot-plug support with connect/disconnect events
- Vibration/rumble support with automatic hardware detection
- CLI tester with positional labels (N/S/E/W)
- Zero config - SDL2 downloads automatically
Why this exists
Browsers only give mapping: "standard" to about 20-30 controllers. Everyone else gets unpredictable button mappings and has to implement config screens. That sucks for game developers.
This library ensures every controller gets standard mappings. Your game code stays simple.
Install
npm install gamepad-nodeSDL2 is installed automatically by @kmamal/sdl. No compilation, no config.
Usage
import { installNavigatorShim } from 'gamepad-node';
installNavigatorShim();
// Same API as browsers
setInterval(() => {
const gamepads = navigator.getGamepads();
for (const gamepad of gamepads) {
if (!gamepad) continue;
if (gamepad.buttons[0].pressed) {
console.log('A button pressed');
}
const leftStickX = gamepad.axes[0];
const leftStickY = gamepad.axes[1];
}
}, 16);Events
const manager = installNavigatorShim();
manager.on('gamepadconnected', (event) => {
console.log('Connected:', event.gamepad.id);
});
manager.on('gamepaddisconnected', (event) => {
console.log('Disconnected:', event.gamepad.id);
});Rumble
const gamepad = navigator.getGamepads()[0];
// vibrationActuator is null if controller doesn't support rumble
if (gamepad?.vibrationActuator) {
await gamepad.vibrationActuator.playEffect('dual-rumble', {
duration: 200,
strongMagnitude: 1.0,
weakMagnitude: 0.5
});
}Test your controllers
npx gamepad-nodeShows all buttons, triggers, sticks, and d-pad in real-time. Face buttons labeled N/S/E/W (North/South/East/West) for positional clarity. Press R to test rumble (if supported).
How it works
Four-tier fallback system with positional mapping priority:
- SDL_GameController with rumble - Keep as-is for rumble support
- SDL_GameController without rumble + db.json match - Force joystick mode for position-aware EmulationStation mappings
- EmulationStation database (321 controllers) - Position-based remapping using community configs
- Fallback - Generic Xbox 360 / PS4 style mapping
Why positional mapping matters
The W3C Gamepad API spec defines buttons by physical position (0=bottom, 1=right, 2=left, 3=top), not by label. But manufacturers print different labels at the same positions:
- Xbox: South=A, East=B, West=X, North=Y
- Nintendo/8BitDo: South=B, East=A, West=Y, North=X
SDL's mapping database uses label-based matching (maps "A button"), which breaks for Nintendo-layout controllers. EmulationStation's database uses position-based matching (maps "south button"), which works universally.
When possible, we use position-aware mappings from EmulationStation. This ensures button 0 is always the bottom button, regardless of what letter is printed on it.
See docs/CONTROLLER_VS_JOYSTICK.md for technical details, or docs/MAPPED_CONTROLLERS.md for the full controller list.
Platform support
Works on macOS (Intel + Apple Silicon), Linux (x64 + arm64), and Windows (x64). SDL2 binaries are downloaded automatically.
Why "better than browsers"?
Most browsers only recognize about 20-30 controllers for standard mapping. Try plugging in a Logitech Precision or some retro USB adapter - you'll get mapping: "" and buttons all over the place.
This library gives every controller standard mappings using position-aware databases. Your game works with anything, zero config required.
Bonus: We also correctly detect rumble support. Browsers often expose vibrationActuator even when hardware doesn't support it - we set it to null if rumble isn't available.
Development
Pure JavaScript on top of @kmamal/sdl, no build step. Run npm install and you're good.
npm test # Basic test
npm run test:events # Events & rumble
npm run test:unit # Unit tests
npx gamepad-node # Interactive testerTerminal Gaming
I'm building this as part of a terminal gaming platform. Combine gamepad-node with webaudio-node and some clever half-block rendering, and you can make full games that run via npx. Check out docs/TERMINAL_GAMING_PLATFORM.md if that sounds interesting.
Credits
Built on @kmamal's SDL2 bindings, which made this whole thing possible. Also using controller databases from SDL_GameControllerDB, Knulli, and Batocera.
License
ISC
