Skip to main content

ProtoHax UserScript

ProtoHax UserScript lets you write your own ProtoHax client modules in TypeScript. A userscript declares a module — its name, category, and options — and wires up listeners against the live game session: game events, packets, the entity/world model, the local player, inventory, and the raw packet connection.

Modules you author this way sit alongside the built-in ones in the client menu. They can be toggled on and off, expose sliders and toggles and color pickers, and read or drive the game exactly like a first-party module.

The @protohax/userscript package

Everything is typed through a single package on npm:

@protohax/userscript

This package is types-only. It ships a single index.d.ts and no runtime code. That is deliberate:

  • The types describe the authoring API and the host's own game model, so your editor autocompletes ctx.session.entityState.localPlayer.jump() and type-checks every packet payload.
  • The values you import from it — defineModule, moduleManager, the entity classes, AbstractBlockLocationTracker, nbt, utils — are resolved at runtime to the host's live singletons when your script is bundled into the client. Your script and the client share one instance of the game state, one module manager, and one (patched) prismarine-nbt.

In other words: you install the package for the types, and the ProtoHax client supplies the implementation when your script runs.

Install

npm install --save-dev @protohax/userscript

tsconfig.json

A minimal config that works with the published declarations:

{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true
}
}

Note — skipLibCheck is required. The published .d.ts vendors the host's packet definitions verbatim, which contain a harmless duplicate declaration. skipLibCheck: true lets your project type-check cleanly while still fully checking your code against the API.

A first module

import { defineModule, ModuleCategory } from "@protohax/userscript";

defineModule(
{ name: "AutoSprint", category: ModuleCategory.Movement },
(options) => {
const speed = options.number("Speed", 1, {
min: 0, max: 5, step: 0.1, displayResolution: 1,
});

return (ctx) => {
ctx.on("tick", () => {
ctx.session.entityState.localPlayer.strafe(speed.value, 1);
});
};
},
);

That is the whole shape of a module: declare options once, then return a per-session setup function that subscribes to events. The next pages walk through each layer.

Where to go next