JavaScript API Guide
Build plugins with JavaScript or TypeScript, hot-reload on save, and access the full game-state API.
Overview
JavaScript plugins run inside an embedded QuickJS engine within the game process. They have
access to the same game-state queries, rendering primitives, and action APIs as native C++ plugins —
exposed through the global titan namespace.
Key advantages of JS plugins:
- Hot-reload — the file watcher reloads
.jsfiles on save, no restart needed - No build toolchain — any text editor works; TypeScript is optional
- Rapid prototyping — iterate quickly on overlay scripts and automation helpers
Plugin Registration
Register a plugin by extending titan.Plugin and calling titan.register():
class MyPlugin extends titan.Plugin {
id = "my_js_plugin";
name = "My JS Plugin";
description = "A sample JavaScript plugin.";
author = "Your Name";
version = "1.0.0";
onGameTick(tick) {
if (tick % 20 === 0) {
titan.log("20 ticks passed!");
}
}
}
titan.register(new MyPlugin());
You can also use an object literal for simpler plugins:
titan.register({
id: "simple_plugin",
name: "Simple Plugin",
onGameTick(tick) {
// ...
}
});
Lifecycle Callbacks
JS plugins support the same lifecycle callbacks as native plugins. All are optional:
| Callback | Description |
|---|---|
onEnable() | Plugin was enabled |
onDisable() | Plugin was disabled |
onGameTick(tick) | Fires every game tick (~600ms) with tick counter |
onClientTick() | Fires every client tick (high frequency) |
onRender() | Fires every frame — draw overlays here |
onNpcSpawned(npc) / onNpcDespawned(npc) | NPC appeared or left the scene |
onPlayerSpawned(player) / onPlayerDespawned(player) | Player appeared or left |
onTileObjectSpawned(obj) / onTileObjectDespawned(obj) | Scene object changes |
onProjectileSpawned(proj) / onProjectileDespawned(proj) | Projectile events |
onProjectileMoved(proj) | Projectile position update |
onMenuOptionClicked(event) | Menu click (set event.consumed = true to block) |
onScriptFired(event) | CS2 client script executed |
onVarbitChanged(event) | Varbit value changed |
onChatMessage(event) | Chat line added |
onItemContainerChanged(event) | Item container contents changed |
onSettingChanged(key) | Setting updated from controller |
Settings
Declare settings using the builder methods on the plugin class:
class MyPlugin extends titan.Plugin {
id = "my_plugin";
name = "My Plugin";
loud = this.boolSetting({ key: "loud", name: "Verbose", default: false });
radius = this.intSetting({ key: "radius", name: "Radius", default: 10, min: 1, max: 50 });
mode = this.comboSetting({
key: "mode", name: "Mode", default: 0,
choices: [{ value: 0, label: "Off" }, { value: 1, label: "On" }]
});
filter = this.stringSetting({ key: "filter", name: "Filter", default: "Chicken" });
onGameTick(tick) {
if (this.loud) {
titan.log("Radius: " + this.radius);
}
}
}
Overlay panels (overlayPanel, SDK v46+)
Register anchored HUD panels with this.overlayPanel({ name, anchor, priority?, style?, render })
from the plugin constructor. Use titan.OverlayAnchor for the initial anchor (e.g.
titan.OverlayAnchor.AboveChatboxRight). The render callback receives a builder with
title, line, progressBar, and set* style methods.
See Panels & Overlays for a full example and anchor table.
The titan Namespace
The global titan object mirrors the C++ SDK's three-shape architecture. In JavaScript,
zero-arg primitive lookups are properties (no parentheses), while calls that take arguments,
allocate collections, or have side effects are methods.
State Facades (titan.state)
State facades give you read (and sometimes write) access to the game's live data. Each facade groups related information — camera, skills, prayers, etc.
titan.state.client
Core client info and the ability to invoke menu actions programmatically.
| Member | Type | Description |
|---|---|---|
.tick | number | Current game tick count |
.plane | number | Current z-plane (0–3) |
.playerCount | number | Number of players in the scene |
.loggedIn | boolean | Whether the client is logged in |
.runEnergy | number | Run energy (0–10000). Divide by 100 for the orb percentage. |
.weight | number | Player weight in kg (signed; negative with weight-reducing gear) |
.accountType | number | Raw account type varbit (titan.Varbits.ACCOUNT_TYPE). SDK v50+ |
.isIronman / .isIronMan | boolean | True for ironman modes, including group variants (isIronMan is an alias). SDK v50+ |
.isGroupIronman / .isGroupIronMan | boolean | True for GIM / HCGIM / UGIM (isGroupIronMan is an alias). SDK v50+ |
.localPlayer | Player | null | The local player entity |
.invokeMenuAction(action) | boolean | Dispatch a fully-specified menu entry; returns whether the host queued it. See titan-plugin-sdk.d.ts for the action object shape. |
const tick = titan.state.client.tick;
const local = titan.state.client.localPlayer;
if (local) {
titan.log(`Tick ${tick} — at ${local.worldX}, ${local.worldY}`);
}
// Programmatic menu action (object form — matches C++ MenuAction::Entry fields)
titan.state.client.invokeMenuAction({
opcode: MenuAction.NPC_FIRST_OPTION,
packedId: npc.index,
param0: 0,
param1: 0,
});
titan.state.camera
Read camera orientation and position. Useful for conditional rendering or camera-relative calculations.
| Member | Type | Description |
|---|---|---|
.yaw() | number | Camera yaw (0–2047) |
.pitch() | number | Camera pitch |
.zoom() | number | Camera zoom level |
.posX() | number | Camera X position in scene coords |
.posY() | number | Camera Y position in scene coords |
.posZ() | number | Camera Z position (height) |
.snapshot() | CameraSnapshot | All values as a single object |
const cam = titan.state.camera.snapshot();
titan.log(`Camera facing yaw=${cam.yaw} pitch=${cam.pitch} zoom=${cam.zoom}`);
titan.state.skills
Read skill levels and experience. Pass a Skill enum value as the argument.
| Method | Returns | Description |
|---|---|---|
.boosted(skill) | number | Current (boosted/drained) level |
.real(skill) | number | Base level |
.experience(skill) | number | Total XP |
const currentHp = titan.state.skills.boosted(Skill.Hitpoints);
const maxHp = titan.state.skills.real(Skill.Hitpoints);
if (currentHp < maxHp * 0.5) {
titan.log("Health below 50%!");
}
titan.state.prayers
Check which prayers are currently active.
| Method | Returns | Description |
|---|---|---|
.isActive(prayer) | boolean | Whether the given prayer is toggled on |
if (!titan.state.prayers.isActive(Prayer.ProtectFromMelee)) {
titan.log("Warning: protect melee is off!");
}
titan.state.vars
Read varbits and varps — the game's key/value configuration store used for quest progress, toggles, counters, etc.
| Method | Returns | Description |
|---|---|---|
.varbit(id) | number | Read a varbit value |
.varp(id) | number | Read a varp (var player) value |
// Check if a quest step is complete
const dragonSlayer = titan.state.vars.varbit(Varbits.QUEST_DRAGON_SLAYER);
if (dragonSlayer >= 6) {
titan.log("Dragon Slayer complete!");
}
titan.state.walk
Issue walk commands. Supports scene coords, world coords, and entity/tile targets.
| Method | Description |
|---|---|
.toScene(x, y) | Walk to scene coordinates |
.toWorld(x, y, plane) | Walk to world coordinates on a given plane |
.to(tile) | Walk to a Tile object (has worldX, worldY, plane) |
.to(worldPos) | Walk to a WorldPos (x, y, plane fields) |
// Walk to Lumbridge spawn
titan.state.walk.toWorld(3222, 3218, 0);
// Walk to an NPC's tile
const banker = titan.queries.npcs().nameContains("Banker").first();
if (banker) titan.state.walk.to(banker.tile);
titan.state.widgets
Interact with the game's widget (interface) tree. Widget IDs are "packed" as (group << 16) | child.
| Method | Returns | Description |
|---|---|---|
.find(packedId) | Widget | null | Look up a widget by packed ID |
.children(parentPackedId) | Widget[] | Get all children of a parent widget |
.findByText(query) | Widget | null | Find first widget whose text contains query |
.pack(group, child) | number | Create a packed widget ID from group + child |
.interact(opcode, packedId, p0, p1) | boolean | Click a widget programmatically; returns whether queued |
.setText(packedId, text) | boolean | Replace live widget text (v1 host limit: 22 bytes). SDK v51+ |
Each widget returned by find / children is a WidgetState: you can read .text,
assign widget.text = "..." (same 22-byte limit), or call widget.setText("..."). SDK v51+.
// Read the prayer orb text
const prayerOrb = titan.state.widgets.find(
titan.state.widgets.pack(160, 4)
);
if (prayerOrb) {
titan.log("Prayer text: " + prayerOrb.text);
}
// Click a widget button
const packedId = titan.state.widgets.pack(270, 15);
titan.state.widgets.interact(MenuAction.CC_OP, packedId, -1, -1);
titan.state.collisions
Read the collision map to check tile blocking flags. Useful for pathfinding or verifying walkability.
| Method | Returns | Description |
|---|---|---|
.flag(plane, x, y) | number | Raw collision flag at scene coordinates |
.isBlocked(plane, x, y, dx, dy) | boolean | Whether movement from (x,y) in direction (dx,dy) is blocked |
The titan.state.collisions.Flag object exposes named constants for collision types:
const Flag = titan.state.collisions.Flag;
// Check if a tile is fully blocked
const flags = titan.state.collisions.flag(0, 50, 50);
if (flags & Flag.BLOCK_MOVEMENT) {
titan.log("Tile is blocked");
}
// Check directional movement
if (titan.state.collisions.isBlocked(0, 50, 50, 1, 0)) {
titan.log("Cannot walk east from here");
}
titan.state.cache
Access game cache definitions — item/NPC/object/varbit configs. Results are definition objects with fields like name, ID, and actions.
| Method | Returns | Description |
|---|---|---|
.item(id) | ItemComposition | Item definition (name, noted, stackable, etc.) |
.npc(id) | NpcComposition | NPC definition (name, actions, combat level) |
.obj(id) | ObjectComposition | Object definition (name, actions, type) |
.varbit(id) | VarbitComposition | Varbit definition (base varp, bit range) |
const def = titan.state.cache.item(4151);
titan.log(`Item: ${def.name}`); // "Abyssal whip"
const npcDef = titan.state.cache.npc(3029);
titan.log(`NPC actions: ${npcDef.actions.join(", ")}`);
titan.state.hider
Toggle visibility of players, NPCs, or scene elements. These are getter/setter properties.
| Property | Type | Description |
|---|---|---|
.players | boolean | Get/set whether other players are hidden |
.npcs | boolean | Get/set whether NPCs are hidden |
.self | boolean | Get/set whether local player is hidden |
.scene | boolean | Get/set whether scene objects are hidden |
// Hide other players for a clean view
titan.state.hider.players = true;
// Restore
titan.state.hider.players = false;
titan.state.script
Invoke CS2 client scripts. Useful for triggering built-in game UI logic.
| Method | Description |
|---|---|
.run(scriptId, intArgs) | Run a CS2 script with int arguments |
.runTyped(...) | Run a CS2 script with typed argument list |
.questState(questId) | Get quest completion state (0/1/2) |
// Check quest progress
const state = titan.state.script.questState(QuestID.DRAGON_SLAYER);
// 0 = not started, 1 = in progress, 2 = complete
titan.state.login
Access login state and credentials. Primarily useful for auto-login plugins.
| Method | Returns / Description |
|---|---|
.state() | number — raw login state ID |
.index() | number — login field index |
.isLoggedIn() | boolean |
.setUsername(str) | Set the username field |
.setPassword(str) | Set the password field |
.setAuthenticator(str) | Set the authenticator code field |
titan.state.idle
Monitor and reset the idle timer. Useful for anti-logout plugins.
| Member | Description |
|---|---|
.remaining() | Ticks remaining before idle logout |
.reset() | Reset the idle timer |
onGameTick(tick) {
if (titan.state.idle.remaining() < 50) {
titan.state.idle.reset();
}
}
titan.state.world
Read world list and hop worlds.
| Method | Description |
|---|---|
.current() | Current world number |
.list() | Array of available worlds |
.hop(id) | Hop to world (from login screen) |
.hopIngame(id) | Hop to world while logged in |
const world = titan.state.world.current();
titan.log(`Currently on world ${world}`);
// Hop to world 302
titan.state.world.hopIngame(302);
titan.state.itemContainer(id)
Read an item container snapshot by container ID. Returns an object with a slots array.
Each slot has id and quantity fields (-1 for empty slots).
// Read the bank container
const bank = titan.state.itemContainer(InventoryID.BANK);
for (const slot of bank.slots) {
if (slot.id !== -1) {
titan.log(`${slot.id} x${slot.quantity}`);
}
}
// Inventory container
const inv = titan.state.itemContainer(InventoryID.INVENTORY);
const usedSlots = inv.slots.filter(s => s.id !== -1).length;
titan.state.itemDef(id)
Shorthand for getting an ItemComposition with full detail (same as titan.state.cache.item(id)).
const whip = titan.state.itemDef(4151);
titan.log(whip.name); // "Abyssal whip"
Query Builders (titan.queries)
Query builders let you search for entities in the game world using chainable filters. Each query starts with a factory method and is refined with filters before collecting results. All queries return snapshots — they do not update live.
Terminal Methods (All Queries)
Every query builder supports these terminal methods to produce results:
| Method | Returns | Description |
|---|---|---|
.first() | Entity | null | First match, or null if none |
.toArray() | Entity[] | All matches as an array |
.count() | number | Number of matches |
.any() | boolean | True if at least one match exists |
.forEach(fn) | void | Iterate all matches with a callback |
titan.queries.npcs()
Search for NPCs in the loaded scene.
| Filter | Description |
|---|---|
.nameContains(str) | NPC name includes substring (case-insensitive) |
.nameEquals(str) | Exact name match |
.id(id) | Match a single NPC ID |
.ids(...ids) | Match any of the given NPC IDs |
.hasAction(str) | Has a right-click action containing str |
.notTargetedByOtherPlayers() | Exclude NPCs being attacked by others |
.interactingWith(actor) / .interactingWithLocal() | Interaction-target filters |
.notInteracting() | Only NPCs with no active target |
.isAnimating() / .notAnimating() / .animation(id) | Animation-state filters |
.overheadActive(icon?) | Overhead icon filters |
.overrideTransform(id) | Morph-transform filter |
.sizeEquals(n) | Tile footprint equals n × n |
.isDead() / .isAlive() | Dead/alive filters (SDK v48+) |
.withHealthBar() / .noHealthBar() | Health-bar presence filters (SDK v48+) |
.healthPercentBelow(x) / .healthPercentAbove(x) | Health threshold filters (SDK v48+) |
.within(radius, origin) | Max tile distance from tile/actor/world point |
.nearestTo(origin) | Return nearest matching NPC (Npc | null) |
.where(fn) | Custom predicate filter |
// Find the nearest un-targeted goblin within 10 tiles
const local = titan.state.client.localPlayer;
const goblin = !local ? null : titan.queries.npcs()
.nameEquals("Goblin")
.notTargetedByOtherPlayers()
.within(10, local.tile)
.nearestTo(local.tile);
if (goblin) goblin.interact("Attack");
titan.queries.players()
Search for players in the loaded scene.
| Filter | Description |
|---|---|
.nameContains(str) | Player name includes substring |
.nameEquals(str) | Exact name match |
.interactingWith(entity) / .interactingWithLocal() | Interaction-target filters |
.notInteracting() | Only players with no active target |
.isAnimating() / .notAnimating() / .animation(id) | Animation-state filters |
.isIdle() / .isSkulled() | Idle/skulled filters |
.overheadActive(icon?) | Overhead icon filters |
.combatLevelAbove(min) / .combatLevelBelow(max) / .combatLevelBetween(lo, hi) | Combat level filters |
.excludingSelf() | Exclude local player from results |
.isDead() / .isAlive() | Dead/alive filters (SDK v48+) |
.withHealthBar() / .noHealthBar() | Health-bar presence filters (SDK v48+) |
.healthPercentBelow(x) / .healthPercentAbove(x) | Health threshold filters (SDK v48+) |
.within(radius, origin) | Max tile distance from tile/actor/world point |
.nearestTo(origin) | Return nearest matching player (Player | null) |
.where(fn) | Custom predicate filter |
// Count nearby players (useful for PvP awareness)
const local = titan.state.client.localPlayer;
const nearbyCount = !local ? 0 : titan.queries.players()
.excludingSelf()
.within(15, local.tile)
.count();
titan.log(`${nearbyCount} players nearby`);
titan.queries.objects(radius?)
Search for game objects (trees, rocks, doors, etc.). The optional radius limits the scene scan area.
| Filter | Description |
|---|---|
.id(id) | Match a single object ID |
.ids(...ids) | Match any of the given IDs |
.hasAction(str) | Has an action containing str |
.ofType(type) | Filter by object type (wall, decoration, etc.) |
.layer(id) | Filter by scene layer (0..3) |
.within(radius, origin) | Max tile distance from tile/actor/world point |
.nearestTo(origin) | Return nearest matching object (TileObject | null) |
.where(fn) | Custom predicate filter |
// Find the nearest bank booth
const booth = titan.queries.objects(20)
.hasAction("Bank")
.nearestTo(titan.state.client.localPlayer);
if (booth) booth.interact("Bank");
titan.queries.groundItems(radius?)
Search for items on the ground.
| Filter | Description |
|---|---|
.id(id) | Match item ID |
.ids(...ids) | Match any of the given IDs |
.minQuantity(n) | Minimum stack size |
.maxQuantity(n) | Maximum stack size |
.canLoot() | Keep only ground items the local account may pick up. SDK v50+ |
.within(radius, origin) | Max tile distance from tile/actor/world point |
.nearestTo(origin) | Return nearest matching ground item (GroundItem | null) |
.where(fn) | Custom predicate filter |
// Loot valuable drops
titan.queries.groundItems(15)
.ids(1617, 1619, 1621) // uncut gems
.forEach((item) => {
item.interact("Take");
});
titan.queries.inventory()
Search items in the player's inventory.
| Filter | Description |
|---|---|
.id(id) | Match item ID |
.ids(...ids) | Match any of the given IDs |
.slot(n) | Match a specific slot index |
.slotsAnyOf(arr) | Match items in any of the given slots |
.slotsBetween(start, end) | Match items in a slot range |
.totalQuantity | Sum of all matched item quantities (property) |
.exists | True if at least one match (property alias for .any()) |
// Check if we have enough runes
const airRunes = titan.queries.inventory().id(556).totalQuantity;
const mindRunes = titan.queries.inventory().id(558).totalQuantity;
if (airRunes >= 3 && mindRunes >= 1) {
titan.log("Ready to cast Wind Strike");
}
titan.queries.projectiles()
Search for active projectiles in the scene (arrows, spells, etc.).
| Filter | Description |
|---|---|
.spotAnim(id) | Match by spot animation ID |
.targetingEntity(idx) | Projectiles aimed at entity index |
.fromEntity(idx) | Projectiles originating from entity index |
.startedAfterTick(t) | Spawned after tick t |
.endsBeforeTick(t) | Ending before tick t |
.activeDuring(t) | Active during tick t |
.within(radius, origin) | Inherited locatable radius filter |
.nearestTo(origin) | Return nearest matching projectile (Projectile | null) |
// Detect incoming teleblock
const tbProjectiles = titan.queries.projectiles().spotAnim(345).count();
if (tbProjectiles > 0) {
titan.log("Teleblock incoming!");
}
Utility Namespaces (titan.utils)
Utilities provide higher-level helpers that combine state reads and actions into convenient APIs. They abstract common patterns like "is the bank open?" or "continue through dialogue."
titan.utils.inventory
Convenience helpers for inventory management beyond raw queries.
| Member | Type | Description |
|---|---|---|
.isOpen | boolean | Whether the inventory tab is visible |
.size | number | Number of occupied slots |
.emptySlots | number | Number of free slots |
.isFull | boolean | True if all 28 slots are occupied |
.isEmpty | boolean | True if inventory is empty |
.getAll() | Item[] | All items in the inventory |
.find(idOrName) | Item | null | Find item by ID or name |
.getSlot(index) | Item | null | Item at specific slot index |
.contains(idOrName) | boolean | Whether the item exists |
.count(idOrName) | number | Quantity of the item |
.containsAny(ids) | boolean | True if any of the IDs are present |
.containsAll(ids) | boolean | True if all IDs are present |
.drop(idOrName) | void | Drop all matching items |
if (titan.utils.inventory.isFull) {
titan.log("Inventory full — banking");
} else {
const logs = titan.utils.inventory.count("Willow logs");
titan.log(`Have ${logs} willow logs`);
}
// Drop all tuna
titan.utils.inventory.drop("Tuna");
titan.utils.dialogue
Handle NPC dialogue, Make-X interfaces, and option selection.
| Member | Type | Description |
|---|---|---|
.inDialogue | boolean | Whether a dialogue box is open |
.continueDialogue() | void | Click "Click here to continue" |
.continueMake() | void | Click "Make" on a Make-X interface |
.hasOption(text) | boolean | Check if a dialogue option exists |
.selectOption(texts) | void | Select first matching option from array |
.handleDialogue(choices) | void | Auto-continue or select from choices array |
.isQuestCompletionOpen | boolean | Whether the quest completion scroll is shown |
.closeQuestCompletion() | void | Dismiss the quest completion scroll |
onGameTick(tick) {
if (titan.utils.dialogue.inDialogue) {
// Auto-handle conversation: continue through text, pick options
titan.utils.dialogue.handleDialogue(["Yes", "I'd like to trade"]);
}
}
titan.utils.combat
Combat-related helpers for special attack and retaliation.
| Member | Type | Description |
|---|---|---|
.getSpecialAttackPercentage() | number | Current spec energy (0–100) |
.isSpecialAttackEnabled() | boolean | Whether spec is toggled on |
.isAutoRetaliateEnabled() | boolean | Whether auto-retaliate is on |
.enableSpecialAttack() | void | Toggle special attack on |
.setAutoRetaliate(enabled) | void | Set auto-retaliate state |
// Use special attack at 50%+
if (titan.utils.combat.getSpecialAttackPercentage() >= 50) {
if (!titan.utils.combat.isSpecialAttackEnabled()) {
titan.utils.combat.enableSpecialAttack();
}
}
titan.utils.magic
Spellbook metadata and catalog objects mirroring titan::utils::Magic from
<titan/utils/magic.h>. Added in SDK v49.
Per-spell records live under titan.utils.magic.Standard, .Ancient, .Lunar, and .Necromancy;
titan.utils.magic.SpellBook names the four spellbook ordinals.
info(spell), currentSpellBook, isAutoCasting, home-teleport timing helpers, and canCast(spell)
reflect live client state. select, cast, and castOn are intentionally stubbed and currently
always return false — real spell dispatch is not wired yet.
const spell = titan.utils.magic.Standard.HIGH_LEVEL_ALCHEMY;
if (titan.utils.magic.canCast(spell)) {
titan.log(titan.utils.magic.info(spell).name);
// titan.utils.magic.cast(spell); // no-op until host dispatch lands
}
titan.utils.equipment
Read and manage equipped items.
| Member | Type | Description |
|---|---|---|
.getAll() | Item[] | All equipped items |
.find(slot) | Item | null | Get item in an equipment slot |
.contains(idOrName) | boolean | Whether the item is equipped |
.count(idOrName) | number | Quantity equipped (for ammo, etc.) |
.unequip(slot) | void | Remove item from slot to inventory |
// Check weapon
const weapon = titan.utils.equipment.find(EquipmentSlot.Weapon);
if (weapon) {
titan.log(`Wielding: ${weapon.name}`);
}
// Unequip cape
titan.utils.equipment.unequip(EquipmentSlot.Cape);
titan.utils.bank
Full banking API — open, deposit, withdraw, search, and manage the bank interface.
| Member | Type | Description |
|---|---|---|
.isOpen | boolean | Whether the bank interface is open |
.getAll() | Item[] | All items in the bank |
.contains(idOrName) | boolean | Whether bank contains the item |
.count(idOrName) | number | Quantity in bank |
.find(idOrName) | Item | null | Find an item in the bank |
.close() | void | Close the bank interface |
.depositAll() | void | Deposit entire inventory |
.depositEquipment() | void | Deposit all worn equipment |
.depositAllOfItem(idOrName) | void | Deposit all of a specific item |
.withdrawItem(idOrName) | void | Withdraw one of the item |
.withdrawAll(idOrName) | void | Withdraw all of the item |
.withdrawAmount(idOrName, qty) | void | Withdraw a specific quantity |
.isNearBank() | boolean | Whether player is near a bank |
.open() | void | Open the nearest bank |
.isPinVisible() | boolean | Whether the bank PIN interface is shown |
.typePin(pin) | void | Enter a bank PIN string |
onGameTick(tick) {
if (!titan.utils.bank.isOpen) {
if (titan.utils.bank.isNearBank()) {
titan.utils.bank.open();
}
return;
}
// Deposit inventory and withdraw supplies
titan.utils.bank.depositAll();
titan.utils.bank.withdrawAmount("Lobster", 10);
titan.utils.bank.withdrawItem("Teleport to house");
titan.utils.bank.close();
}
Keyboard (titan.keyboard)
Send keystrokes to the game client. Useful for typing in chat, entering amounts, or triggering hotkeys.
| Method | Description |
|---|---|
.sendString(str) | Instantly send a string (no delay between chars) |
.sendKey(key, mods?) | Send a single key press with optional modifiers |
.typeString(str, opts?) | Type a string with human-like delays between characters |
.cancelTypeString() | Cancel an in-progress typeString operation |
.isTyping() | Whether a typeString operation is in progress |
The opts parameter for typeString accepts minDelay and maxDelay in milliseconds.
Key names are string constants (e.g. "Enter", "Escape", "Space", "Tab", "F1"–"F12", "1"–"9").
Modifiers: "Shift", "Ctrl", "Alt".
// Type a bank PIN amount
titan.keyboard.sendString("1000");
titan.keyboard.sendKey("Enter");
// Human-like typing in chat
titan.keyboard.typeString("Hello world!", { minDelay: 40, maxDelay: 120 });
// Press F1 to open inventory tab
titan.keyboard.sendKey("F1");
// Ctrl+Shift shortcut
titan.keyboard.sendKey("1", "Ctrl");
Chat (titan.chat)
Add messages to the chatbox or send public chat.
| Method | Description |
|---|---|
.add(type, name, message, sender) | Add a chat message with full control over type, name, and sender |
.system(message) | Add a system/game message (green text) |
.say(message) | Send a public chat message as the local player |
// Notify the player via chatbox
titan.chat.system("Plugin: Task complete!");
// Add a styled message
titan.chat.add(0, "MyPlugin", "Hello from plugin!", "");
// Say something in public chat — signature is say(name, message); see titan-plugin-sdk.d.ts
const lp = titan.state.client.localPlayer;
if (lp) {
titan.chat.say(lp.name, "gg");
}
Overlay Rendering (titan.overlay)
All drawing functions must be called from inside titan.onRender(...). They draw for one frame only —
call them every frame for persistent visuals. Colours follow the host renderer: match C++ overlay docs (typically ABGR uint32_t bit layout — see Panels & Overlays).
World-space drawing
| Method | Description |
|---|---|
.tileQuad(tileX, tileZ, plane, fillColor, outlineColor) | Filled tile quad (scene axes; second parameter is scene Y) |
.tileRegion(minTX, minTZ, maxTX, maxTZ, plane, fillColor, outlineColor) | Rectangle of tiles |
.textAtWorld(worldX, worldY, worldZ, text, color, centered?) | Text at absolute world position (worldZ is plane) |
.tileHeight(preciseX, preciseZ, plane) | Terrain height at precise scene coordinates |
Entity / object drawing
| Method | Description |
|---|---|
.entityBox(entity, color, height?) | AABB around Npc or Player |
.entityClickbox(entity, outline, fill?) | Projected clickbox (outline / optional fill colours) |
.entityHull(entity, outline, fill?) | Convex hull outline |
.tileObjectClickbox(obj, outline, fill?) | Loc clickbox |
.tileObjectHull(obj, outline, fill?) | Loc hull |
Screen-space drawing
| Method | Description |
|---|---|
.screenText(x, y, text, color) | Text at screen pixel coordinates |
.screenRect(x, y, w, h, color) | Filled rectangle |
.screenLine(x1, y1, x2, y2, color, thickness?) | Line between two screen points |
Projection helpers
| Method | Returns | Description |
|---|---|---|
.worldToScreen(worldX, worldY, worldZ) | ScreenPoint | null | World coordinates to screen (worldZ is plane) |
.tileToScreen(tileX, tileZ, plane, heightOffset?) | ScreenPoint | null | Scene tile centre to screen |
titan.onRender("AboveScene", () => {
const local = titan.state.client.localPlayer;
if (!local) return;
const t = local.tile;
titan.overlay.tileQuad(t.x, t.y, t.plane, 0x4400ff00, 0xff00ff00);
titan.queries.npcs().within(8, local.tile).forEach((npc) => {
titan.overlay.entityClickbox(npc, 0xffff0000, 0x30ff0000);
});
titan.queries.objects(15).hasAction("Bank").forEach((obj) => {
titan.overlay.tileObjectHull(obj, 0xff00ffff, 0x3000ffff);
});
titan.overlay.screenText(10, 10, "My Plugin v1.0", 0xffffffff);
titan.overlay.screenRect(5, 5, 200, 25, 0x88000000);
const pt = titan.overlay.worldToScreen(3200, 3200, 0);
if (pt) {
titan.overlay.screenText(pt.x, pt.y, "Marker", 0xffffff00);
}
});
Entity Objects
Entities returned by queries have methods and properties for inspection and interaction. These are snapshot objects — they reflect state at query time.
NPC
Returned by titan.queries.npcs(). Implements Npc in titan-plugin-sdk.d.ts (includes ActorBase fields).
| Member | Type | Description |
|---|---|---|
ActorBase: hashIndex, tileX, tileY, plane, worldX, worldY, preciseX, preciseY, orientation, animation, interactingIndex, interactingType, interactingPhase, entityPtr, tile, worldPoint, distanceTo, interacting() → Actor | null | ||
.name | string | NPC name |
.id | number | NPC composition id |
.overrideTransform | number | Transform override index from definition pipeline |
.sizeX / .sizeY | number | Footprint in tiles |
.isAnimating | boolean | animation !== -1 |
.actions | string[] | Cached right-click actions |
.overheadIcon | number | Cache-default head icon ordinal (< 0 = none) |
.hasHeadIconOverride | boolean | Runtime head-icon override |
.healthRatio | number | Headbar fill, or -1 when none (SDK v48+) |
.healthScale | number | Headbar max, or -1 when none (SDK v48+) |
.hasHealthBar | boolean | Any active health bar (SDK v48+) |
.hasAction(action) | boolean | Substring match on actions |
.interact(action) | boolean | Queue NPC menu entry |
.isOverheadActive(icon?) | boolean | Overhead icon predicate |
.healthPercent() | number | Health in [0,1] or -1 (SDK v48+) |
.isDead() | boolean | Health bar active and ratio at zero (SDK v48+) |
const npc = titan.queries.npcs().nameEquals("Guard").first();
if (npc && npc.animation === -1) {
npc.interact("Pickpocket");
}
Player
Returned by titan.queries.players() and titan.state.client.localPlayer.
| Member | Type | Description |
|---|---|---|
ActorBase: same positional / interaction fields as NPC (see titan-plugin-sdk.d.ts) | ||
.name | string | Display name |
.combatLevel | number | Combat level |
.isHidden | boolean | Hidden by filters |
.isStationary | boolean | No pending movement |
.isAnimating | boolean | Animation active |
.isIdle | boolean | Stationary and not animating |
.overheadIcon | number | Prayer icon ordinal (< 0 = none) |
.skullIcon | number | Skull icon ordinal (< 0 = none) |
.healthRatio | number | Headbar fill, or -1 when none (SDK v48+) |
.healthScale | number | Headbar max, or -1 when none (SDK v48+) |
.hasHealthBar | boolean | Any active health bar (SDK v48+) |
.isOverheadActive(icon?) | boolean | Prayer icon predicate |
.isSkulled() | boolean | Wilderness skull visible |
.healthPercent() | number | Health in [0,1] or -1 (SDK v48+) |
.isDead() | boolean | Health bar active and ratio at zero (SDK v48+) |
.interacting() | Actor | null | Interaction target |
const local = titan.state.client.localPlayer;
if (local && local.isIdle) {
titan.log("Player is idle — time to do something!");
}
TileObject
Returned by titan.queries.objects().
| Member | Type | Description |
|---|---|---|
.name | string | Object name |
.type | string | Classification string (same role as C++ typeName()) |
.id | number | Loc definition id |
.sizeX / .sizeY | number | Footprint |
.layer | number | Wall / Decor / Scenery / GroundDecor (or -1) |
.tile | Tile | Scene tile (x, y, plane) |
.actions | string[] | Right-click actions |
.hasAction(action) | boolean | Whether a right-click option exists |
.interact(action) | boolean | Queue loc menu entry |
const tree = titan.queries.objects(10).hasAction("Chop down").first();
if (tree) {
titan.log(`Found ${tree.name} at ${tree.tile.x}, ${tree.tile.y}`);
tree.interact("Chop down");
}
GroundItem
Returned by titan.queries.groundItems().
| Member | Type | Description |
|---|---|---|
.name | string | Item name |
.id | number | Item id |
.quantity | number | Stack size |
.ownershipType | number | Raw ClientObj ownership; compare to titan.GroundItemOwnership (unknown reads as 0xFFFFFFFF). SDK v50+ |
.canLoot() | boolean | Whether this drop is lootable for the current account. SDK v50+ |
.tile | Tile | Location on the ground |
.interact(action) | boolean | Queue ground-item action |
Item (Inventory/Equipment)
Returned by inventory and equipment queries or utils.
| Member | Type | Description |
|---|---|---|
.id | number | Item ID |
.name | string | Item name |
.quantity | number | Stack size |
.slot | number | Inventory slot index |
.interact(action) | boolean | Queue inventory action |
.useOn(target) | boolean | Use on item / NPC / object |
// Eat food when low HP
const food = titan.utils.inventory.find("Shark");
if (food) food.interact("Eat");
// Use knife on logs
const knife = titan.utils.inventory.find("Knife");
const logs = titan.utils.inventory.find("Willow logs");
if (knife && logs) knife.useOn(logs);
Projectile
Returned by titan.queries.projectiles().
| Member | Type | Description |
|---|---|---|
.plane | number | Map plane |
.startX / .startZ | number | Start position (engine space) |
.targetX / .targetZ | number | Target position |
.sourceEntity / .targetEntity | number | Encoded entity ids from client |
.spotAnimId | number | Graphics id |
.startTick / .endTick | number | Lifecycle ticks |
.sceneX / .sceneY / .sceneZ | number | Fine scene coordinates |
.yaw / .pitch | number | Orientation |
.hasMoved | boolean | Interpolation / motion flag |
Built-in Enums
Enums are available as global objects in the JS runtime. Use them anywhere you need to pass a skill, prayer, equipment slot, or menu action type.
Skill
| Constant | Constant | Constant |
|---|---|---|
Skill.Attack | Skill.Hitpoints | Skill.Mining |
Skill.Strength | Skill.Agility | Skill.Smithing |
Skill.Defence | Skill.Herblore | Skill.Fishing |
Skill.Ranged | Skill.Thieving | Skill.Cooking |
Skill.Prayer | Skill.Crafting | Skill.Firemaking |
Skill.Magic | Skill.Fletching | Skill.Woodcutting |
Skill.Runecraft | Skill.Slayer | Skill.Farming |
Skill.Construction | Skill.Hunter | Skill.Overall |
Prayer
Ordinal values match _titan.Prayer in the JS bootstrap (UPPER_SNAKE_CASE keys).
| Constant | Constant | Constant |
|---|---|---|
Prayer.THICK_SKIN | Prayer.PROTECT_FROM_MAGIC | Prayer.PIETY |
Prayer.BURST_OF_STRENGTH | Prayer.PROTECT_FROM_MISSILES | Prayer.RIGOUR |
Prayer.CLARITY_OF_THOUGHT | Prayer.PROTECT_FROM_MELEE | Prayer.AUGURY |
Prayer.SHARP_EYE | Prayer.EAGLE_EYE | Prayer.SMITE |
Prayer.MYSTIC_WILL | Prayer.MYSTIC_MIGHT | Prayer.REDEMPTION |
Prayer.ROCK_SKIN | Prayer.STEEL_SKIN | Prayer.CHIVALRY |
HeadIcon
Overhead prayer/skull icons visible above players.
| Constant | Description |
|---|---|
HeadIcon.MELEE | Protect from Melee icon |
HeadIcon.RANGED | Protect from Missiles icon |
HeadIcon.MAGIC | Protect from Magic icon |
HeadIcon.RETRIBUTION | Retribution icon |
HeadIcon.SMITE | Smite icon |
HeadIcon.REDEMPTION | Redemption icon |
HeadIcon.SKULL | PvP skull |
EquipmentSlot
| Constant | Constant | Constant |
|---|---|---|
EquipmentSlot.Head | EquipmentSlot.Body | EquipmentSlot.Ammo |
EquipmentSlot.Cape | EquipmentSlot.Legs | EquipmentSlot.Ring |
EquipmentSlot.Amulet | EquipmentSlot.Hands | EquipmentSlot.Shield |
EquipmentSlot.Weapon | EquipmentSlot.Feet |
InventoryID
Container IDs for use with titan.state.itemContainer().
| Constant | Description |
|---|---|
InventoryID.INVENTORY | Player inventory (28 slots) |
InventoryID.EQUIPMENT | Worn equipment |
InventoryID.BANK | Bank storage |
InventoryID.TRADE | Trade window |
MenuAction
Opcodes for programmatic menu actions via invokeMenuAction or widget interaction.
| Constant | Description |
|---|---|
MenuAction.NPC_FIRST_OPTION | First NPC right-click option |
MenuAction.NPC_SECOND_OPTION | Second NPC option |
MenuAction.NPC_THIRD_OPTION | Third NPC option |
MenuAction.NPC_FOURTH_OPTION | Fourth NPC option |
MenuAction.NPC_FIFTH_OPTION | Fifth NPC option |
MenuAction.GAME_OBJECT_FIRST_OPTION | First object option |
MenuAction.GAME_OBJECT_SECOND_OPTION | Second object option |
MenuAction.GROUND_ITEM_FIRST_OPTION | First ground item option |
MenuAction.WALK | Walk-here action |
MenuAction.CC_OP | Widget click/interact |
MenuAction.CC_OP_LOW_PRIORITY | Low-priority widget click |
MenuAction.WIDGET_TARGET | Use item/spell on target |
MenuAction.PLAYER_FIRST_OPTION | First player option (e.g. Follow) |
MenuAction.ITEM_USE | Use item |
OverlayLayer
Controls draw order for overlay elements.
| Constant | Description |
|---|---|
OverlayLayer.AboveScene | Drawn above the 3D scene, below widgets |
OverlayLayer.AboveWidgets | Drawn above all widgets (topmost) |
Varbits & VarPlayerID
Named constants for commonly-used varbit and varp IDs. Avoids magic numbers in your code:
// Named varbit constants
const poisoned = titan.state.vars.varbit(Varbits.POISONED);
const runToggled = titan.state.vars.varp(VarPlayerID.RUN_ENABLED); // 1 = on, 0 = off
const energy = titan.state.client.runEnergy; // 0-10000
const weightKg = titan.state.client.weight; // signed kg
const specialAttack = titan.state.vars.varp(VarPlayerID.SPECIAL_ATTACK);
Plugin Interaction (titan.plugins)
Manage other plugins at runtime — enable, disable, or query their state. Useful for meta-plugins that orchestrate multiple scripts.
| Method | Returns | Description |
|---|---|---|
.all() | PluginHandle[] | All registered plugins |
.get(id) | PluginHandle | Get plugin by ID (throws if not found) |
.find(id) | PluginHandle | null | Find plugin by ID (null if not found) |
.self | PluginHandle | Handle to the current plugin |
A PluginHandle has the following members:
| Member | Type | Description |
|---|---|---|
.id | string | Plugin ID |
.name | string | Display name |
.isEnabled() | boolean | Whether the plugin is currently enabled |
.enable() | void | Enable the plugin |
.disable() | void | Disable the plugin |
.toggle() | void | Toggle enabled state |
.setEnabled(v) | void | Set enabled state explicitly |
// Disable another plugin when entering combat
onGameTick(tick) {
const local = titan.state.client.localPlayer;
if (local && local.interacting()) {
const skiller = titan.plugins.find("woodcutting_plugin");
if (skiller && skiller.isEnabled()) {
skiller.disable();
titan.chat.system("Paused woodcutting — in combat");
}
}
}
// List all plugins
titan.plugins.all().forEach((p) => {
titan.log(`${p.name} [${p.id}] enabled=${p.isEnabled()}`);
});
Logging
titan.log("Plain message");
titan.logf("Formatted: tick=%d hp=%d", tick, hp);
Scheduling
Queue work to run on a specific thread. Useful when your logic runs on a callback but needs to invoke an action on a different thread.
// Queue work onto the game thread (safe for actions)
titan.runOnClientTick(() => {
titan.state.walk.toWorld(3200, 3200, 0);
});
// Queue work onto the render thread (safe for overlay calls)
titan.runOnRender(() => {
titan.overlay.screenText(10, 10, "Deferred draw", 0xffffffff);
});
TypeScript Support
The SDK ships with titan-plugin-sdk.d.ts — full TypeScript type definitions for every
titan.* API, callback, entity type, and enum. Reference it at the top of your file:
/// <reference path="../titan-plugin-sdk.d.ts" />
class MyPlugin extends titan.Plugin {
id = "my_ts_plugin";
name = "My TS Plugin";
onGameTick(tick: number) {
const local: PlayerState | null = titan.state.client.localPlayer;
if (!local) return;
titan.logf("Position: %d, %d", local.worldX, local.worldY);
}
}
titan.register(new MyPlugin());
Compile with tsc or esbuild and drop the .js output in
~/.titanclient/plugins/.
Performance Considerations
- Each JS callback has an opcode budget of 1,000,000 operations. Exceeding this interrupts the callback to prevent the game from freezing.
- Prefer game-tick callbacks for periodic logic; avoid heavy computation in
onClientTickoronRender. - Query results are snapshots — call
.toArray()once and iterate the result, rather than making repeated queries in a loop.
Hot Reload
The plugin host watches plugin directories for .js file changes. When you save a file, the
engine unloads the old script and loads the new one — preserving the plugin's enabled state but
resetting all runtime state. This happens on the game thread and is seamless during gameplay.
Tip: Use hot-reload for rapid iteration during development. For complex state that should survive reloads, persist it in settings rather than local variables.