The bettertouchtool npm package
bettertouchtool is the official, typed JavaScript /
TypeScript client for BetterTouchTool's scripting API. It wraps every scripting function described in this
section (trigger_named, trigger_action, variables, clipboard, floating menus, widgets, presets, …) with
typed methods, ships builders for actions and triggers, and includes a small btt command line tool.
Source & issues: github.com/folivoraAI/bettertouchtool-js (MIT, AI generated & maintained by folivora.AI).
npm install bettertouchtool
Where it runs
| Environment | Transport used | Setup in BTT |
|---|---|---|
| Node.js (≥ 18) on the same Mac | Unix socket (/tmp/com.hegenberg.BetterTouchTool.sock) | Settings → Scripting BTT → Command Line → Enable Socket Server |
| Node.js, browsers, other devices | Webserver (HTTP, GET or POST) | Settings → Scripting BTT → Webserver |
| Inside BTT itself (Run Real JavaScript, WebViews, floating HTML menus, script widgets) | in-process callBTT() | nothing |
new Btt() picks the best available transport automatically (in-process → socket → http); Btt.socket(),
Btt.http({ port, sharedSecret }) and Btt.inProcess() force one.
Examples
import { Btt, actions, triggers } from "bettertouchtool";
const btt = Btt.http({ port: 64472, sharedSecret: "my-secret" }); // or Btt.socket()
// scripting functions as typed methods
await btt.triggerNamed("My Named Trigger");
const app = await btt.getStringVariable("BTTActiveAppBundleIdentifier");
await btt.vars.set("my_counter", 5, { persistent: true });
const clip = await btt.getClipboardContent();
// run any predefined action
await btt.triggerAction(actions.showHUD("Hello", { detail: app, duration: 2 }));
await btt.triggerAction(actions.sendShortcut("cmd+shift+4"));
// several actions in one round trip
await btt.chain().launchApp("com.apple.Safari").delay(0.5).sendShortcut("cmd+t").pasteText("folivora.ai").run();
// create triggers from code
const handle = await btt.addNewTrigger(
triggers.keyboardShortcut("cmd+shift+k", [actions.showHUD("⌘⇧K pressed")], { description: "created from Node" }),
);
await handle.invoke();
await handle.delete();
// anything else: raw scripting call (parameter names as documented in this section)
await btt.call("update_menu_item", { item_uuid: "…", json: { BTTMenuItemText: "hi" } });
ActionType contains all BTTPredefinedActionType ids by name (ActionType.SHOW_HUD === 254), and
import { actionCatalog } from "bettertouchtool/catalog" gives you the complete
action and trigger reference data with
documented parameters (actionCatalog.search("floating menu")).
Inside BetterTouchTool: require("bettertouchtool")
Since BTT 6.735 a copy of the library ships inside BetterTouchTool and BTT's JavaScript engine has a
CommonJS-style require(). Nothing to install:
// Run Real JavaScript action – shows a HUD, returns the frontmost app
async function main() {
const { Btt, actions } = require("bettertouchtool");
const btt = Btt.inProcess(); // talks to BTT directly, no socket / webserver needed
const app = await btt.getStringVariable("BTTActiveAppBundleIdentifier");
await btt.triggerAction(
actions.showHUD("Hello from inside BTT 🖐", { detail: `frontmost app: ${app}`, duration: 2 }),
);
await btt.chain().sendShortcut("cmd+s").delay(0.3).showHUD("saved").run(); // sequences work too
return app; // becomes the action's result
}
require() resolves, in this order:
- modules bundled with BTT – currently
bettertouchtool(alsorequire("bettertouchtool").catalogfor the action/trigger reference data) - your own modules in
~/Library/Application Support/BetterTouchTool/JavaScriptModules/, loaded by name:<name>.js/<name>.cjs, a package folder<name>/(package.json→exports["."]ormain, elseindex.js/index.cjs), nested names likerequire("utils/parse"); with or without the extension - a file path (
require("~/Scripts/my-bundle.cjs"),require("/absolute/path.js"),require("./relative"))
module.exports / exports work like in Node, and IIFE bundles that assign to a global work as well.
Modules are evaluated once per JavaScript context and cached (require.cache). Editing a module file is
picked up automatically – BTT compares the file's modification date on every require() and re-evaluates
changed modules, so you can iterate without restarting BTT. To force it: require.reload("name") (re-loads
one module and returns it), require.reload() (drops the whole cache) or delete require.cache["name"].
(Auto-reload and package folders: BTT 6.736+; require() itself: 6.735+.)
Using other npm packages inside BetterTouchTool
The engine itself has no Node.js module resolution, so third-party npm packages must be bundled into one
CommonJS file first (they must not depend on Node-only APIs such as fs or child_process):
# my-module.js: import dayjs from "dayjs"; module.exports = { today: () => dayjs().format("YYYY-MM-DD") };
npx esbuild my-module.js --bundle --format=cjs --platform=browser \
--outfile=~/Library/Application\ Support/BetterTouchTool/JavaScriptModules/my-module.js
async function main() {
const { today } = require("my-module");
return today();
}
For BTT versions before 6.735 use eval(readFile("/path/to/bundle.js")) with an IIFE bundle instead.
Command line
npx bettertouchtool help
btt trigger-named "My Trigger"
btt get-var BTTActiveAppBundleIdentifier
btt hud "Hello" --detail "from the shell"
btt triggers list --type BTTTriggerTypeKeyboardShortcut
btt actions search hud
btt call trigger_action json='{"BTTPredefinedActionType":254,"BTTAdditionalActionData":{"BTTActionHUDTitle":"hi"}}'
Connection flags --socket, --url, --port, --secret (or env BTT_SOCKET, BTT_URL, BTT_PORT,
BTT_SECRET); by default it uses the socket when available. This complements the built-in
bttcli tool that ships with BTT.