Typescript Scripting Guide

TypeScript Scripting Guide

This guide shows how to write PowBot Desktop scripts in TypeScript and compile them to Lua with TypeScriptToLua.

PowBot publishes a TypeScript declaration file named pow_api.d.ts. The desktop client downloads and caches it at %USERPROFILE%/.powbot/pow_api.d.ts next to pow_api.d.lua. That file describes the global PowBot APIs such as logger, game_objects, components, camera, and script_manager.

For the official TypeScriptToLua docs, start here:

When To Use This

Use this workflow when you want:

  • Type checking for your script state and options
  • Auto-complete against the PowBot API in TypeScript-aware editors
  • Refactors and error checking before you run the script

If you want to write plain Lua directly, use the existing Lua guides and pow_api.d.lua docs instead.

Project Layout

A minimal TypeScriptToLua project can look like this:

my-script/
  manifest.json
  package.json
  tsconfig.json
  src/
    main.ts

Install TypeScriptToLua

Create a package.json and install the compiler locally:

npm install --save-dev typescript typescript-to-lua

This follows the recommended local-install workflow from the TypeScriptToLua docs.

tsconfig.json

PowBot Desktop runs on Lua 5.4, so set luaTarget to 5.4.

{
  "$schema": "https://raw.githubusercontent.com/TypeScriptToLua/TypeScriptToLua/master/tsconfig-schema.json",
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["ESNext"],
    "module": "CommonJS",
    "moduleResolution": "Node",
    "strict": true,
    "rootDir": "src",
    "outDir": "dist"
  },
  "include": ["src/**/*.ts", "%USERPROFILE%/.powbot/pow_api.d.ts"],
  "tstl": {
    "luaTarget": "5.4",
    "buildMode": "default",
    "noImplicitGlobalVariables": true
  }
}

This setup keeps your generated Lua in dist/ and ensures TypeScript sees both your source files and the local declaration reference.

**Be sure to replace %USERPROFILE%/.powbot/pow_api.d.ts with the full path e.g. C:/Users/Username/.powbot/pow_api.d.ts

package.json

{
  "private": true,
  "scripts": {
    "build": "tstl",
    "watch": "tstl --watch"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "typescript-to-lua": "^1.0.0"
  }
}

manifest.json

Your compiled Lua file still needs a normal PowBot manifest.

{
  "name": "Quester",
  "version": "0.1.0",
  "main": "dist/main.lua"
}

Build with:

npm run build

During local development, use this command to recompile as you make changes:

npm run watch

Writing A PowBot Script In TypeScript

The simplest pattern is:

  1. Define a Script interface for your lifecycle methods.
  2. Build a plain object that implements it.
  3. Export the script.
interface Script {
  name: string;
  version: string;
  status_text: string;
  options: LuaTable;
  state: string;
  on_config_request(this: Script, values: LuaTable): LuaTable;
  on_button_clicked(this: Script, btn_id: string | number, values: LuaTable): LuaTable;
  on_config_changed(this: Script, values: LuaTable, data?: LuaTable): LuaTable;
  on_start(this: Script, options_table?: LuaTable): boolean;
  poll(this: Script): void;
  on_pause(this: Script): void;
  on_resume(this: Script): void;
  on_stop(this: Script): void;
}

const script: Script = {
  name: "Quester",
  version: "0.1.0",
  status_text: "Idle",
  options: {},
  state: "IDLE",

  on_config_request(this: Script, values: LuaTable): LuaTable {
    this.options = values;
    logger.info(`Config requested for ${this.name}`);
    return values;
  },

  on_button_clicked(this: Script, btn_id: string | number, values: LuaTable): LuaTable {
    this.status_text = `Button clicked: ${btn_id}`;
    logger.info(this.status_text);
    return values;
  },

  on_config_changed(this: Script, values: LuaTable, data?: LuaTable): LuaTable {
    this.options = values;
    this.status_text = "Config updated";
    logger.info(this.status_text);
    return values;
  },

  on_start(this: Script, options_table?: LuaTable): boolean {
    this.options = options_table ?? this.options;
    this.status_text = `Script started: ${this.name} v${this.version}`;
    return true;
  },

  poll(this: Script): void {
    this.state = "RUNNING";
    this.status_text = `Polling in state ${this.state}`;

    const obj = game_objects.find_first({ name: "Gate" });

    if (obj && !obj.interact({ action: "Open" })) {
      camera.turn_to(obj);
    }

    logger.info(this.status_text);
    sleep(100);
  },

  on_pause(this: Script): void {
    this.state = "PAUSED";
    this.status_text = `${this.name} paused`;
    logger.info(this.status_text);
  },

  on_resume(this: Script): void {
    this.state = "RUNNING";
    this.status_text = `${this.name} resumed`;
    logger.info(this.status_text);
  },

  on_stop(this: Script): void {
    this.state = "STOPPED";
    this.status_text = `${this.name} stopped`;
    logger.info(this.status_text);
  }
};

export = script;

Why this: Script Matters

TypeScriptToLua treats function context explicitly. The this: Script parameter tells the compiler that these methods are instance-style methods and keeps this strongly typed inside each lifecycle function.

That is directly aligned with the TypeScriptToLua self and this guidance:

  • Use this: Script when the function should behave like a Lua method with self
  • Use this: void when a callable must not receive a Lua self

For PowBot lifecycle methods such as on_start, poll, and on_stop, you want the script table as context, so this: Script is the correct choice.

Calling PowBot APIs

Use normal TypeScript property access:

logger.info("Hello");
const tree = game_objects.find_first({ name: "Tree" });
const player = players.local_player();

The provided declarations are written so TypeScriptToLua can emit the correct Lua call style for the PowBot runtime.

Compiled Output

In the sample tsconfig.json above, outDir is set to dist, so that configuration writes output to:

dist/
  main.lua

Without outDir, TypeScriptToLua writes emitted .lua files next to the corresponding .ts source files.

Example default layout:

src/
  main.ts
  main.lua

Use the compiled .lua file as your PowBot script entrypoint in manifest.json.

Notes And Limitations

  • pow_api.d.ts is generated from the same source as pow_api.d.lua, so editor help and runtime docs stay aligned.
  • If you need to model callbacks that must not receive a Lua context, follow the official this: void guidance from the TypeScriptToLua self-parameter docs.
  1. Create manifest.json and point its main field at your compiled Lua entrypoint, usually dist/main.lua.
  2. Add a local types.d.ts that references %USERPROFILE%/.powbot/pow_api.d.ts.
  3. Write your script in src/main.ts.
  4. Run npm run watch while developing so your Lua output stays up to date as you edit.
  5. Load the generated Lua file in PowBot Desktop.
  6. Use npm run build when you want a one-off build instead of watch mode.