Capturing global keyboard shortcuts like Ctrl + C or Shift + Alt + S can be incredibly useful for building productivity tools, real-time overlays, or accessibility utilities. But doing it cleanly — without logging noisy or repeated key events — requires careful handling of modifier keys and key state.
This post walks through how to build a global key combo tracker in Node.js on Windows, and explains the logic behind each part of the implementation.
🧩 The Challenge
Most global key listeners log every key event — including repeated Ctrl, Shift, or Alt presses — which leads to noisy, unreadable logs like:
LEFT CTRL
LEFT CTRL
LEFT CTRL
A
To build a clean and useful tracker, we need to:
- ❌ Ignore modifier keys when pressed alone
- ✅ Log only meaningful combinations (e.g.
Ctrl + A) - ✅ Normalize key names like
LEFT CTRL→Ctrl - ✅ Prevent repeated logs while keys are held
- ✅ Support global tracking, even when the app is not focused
⚙️ Implementation
1. Install the Listener
We use node-global-key-listener, a native module that captures global key events across the OS.
npm install node-global-key-listener
2. Core Logic
const { GlobalKeyboardListener } = require("node-global-key-listener");
const keyboard = new GlobalKeyboardListener();
const heldModifiers = new Set();
const loggedCombos = new Set();
const MODIFIERS = new Set([
"LEFT CTRL", "RIGHT CTRL",
"LEFT SHIFT", "RIGHT SHIFT",
"LEFT ALT", "RIGHT ALT",
"LEFT META", "RIGHT META"
]);
keyboard.addListener((e) => {
const key = e.name.toUpperCase();
if (e.state === "DOWN") {
if (MODIFIERS.has(key)) {
heldModifiers.add(normalizeModifier(key));
} else {
const combo = heldModifiers.size > 0
? [...heldModifiers, key].join(" + ")
: key;
if (!loggedCombos.has(combo)) {
loggedCombos.add(combo);
console.log(combo);
}
}
} else if (e.state === "UP") {
if (MODIFIERS.has(key)) {
heldModifiers.delete(normalizeModifier(key));
} else {
loggedCombos.clear();
}
}
});
function normalizeModifier(key) {
if (key.includes("CTRL")) return "Ctrl";
if (key.includes("SHIFT")) return "Shift";
if (key.includes("ALT")) return "Alt";
if (key.includes("META")) return "Meta";
return key;
}
🧠 Why This Works
🔹 Modifier Filtering
We define a set of known modifier keys (LEFT CTRL, RIGHT SHIFT, etc.) and track them in heldModifiers. These keys are ignored when pressed alone — they only contribute to a combo.
🔹 Normalization
The OS reports keys like LEFT CTRL and RIGHT CTRL separately. For clarity and consistency, we normalize them to a single label (Ctrl, Shift, etc.) using the normalizeModifier() function.
This ensures that:
-
LEFT CTRL + AandRIGHT CTRL + Aboth log asCtrl + A - The output is clean and human-readable
🔹 Combo Construction
When a non-modifier key is pressed:
- If modifiers are held → log the full combo (e.g.
Ctrl + A) - If no modifiers are held → log the key alone (e.g.
A)
🔹 Duplicate Prevention
We use a loggedCombos set to ensure each combo is logged only once per press. This avoids repeated logs when holding keys.
🔹 Reset on Key Release
When a non-modifier key is released, we clear the loggedCombos set so the next press can be logged again.
🧪 Example Output
Ctrl + C
Shift + Alt + S
Escape
A
- ✅
Ctrlalone → ignored - ✅
A→ logged - ✅
Ctrl + A→ logged once - ✅ Holding keys → no spam
🚀 What’s Next?
This Node.js core is a clean foundation for more advanced tools. You could extend it by:
- 🖼️ Wrapping it in an Electron app with a floating overlay
- 💾 Logging combos to a file or clipboard
- 🎨 Styling the UI with themes and transparency
- 📦 Packaging it as a cross-platform desktop utility
🧠 Conclusion
This project demonstrates how to:
- Handle global keyboard input in Node.js
- Filter and normalize noisy key events
- Design a clean shortcut-tracking system
It’s a great one-day build with real-world utility — and a perfect launchpad for more advanced desktop tools.
You can find the full source code on GitHub: global-key-logger.

