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 .js files 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:

CallbackDescription
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.

MemberTypeDescription
.ticknumberCurrent game tick count
.planenumberCurrent z-plane (0–3)
.playerCountnumberNumber of players in the scene
.loggedInbooleanWhether the client is logged in
.runEnergynumberRun energy (0–10000). Divide by 100 for the orb percentage.
.weightnumberPlayer weight in kg (signed; negative with weight-reducing gear)
.accountTypenumberRaw account type varbit (titan.Varbits.ACCOUNT_TYPE). SDK v50+
.isIronman / .isIronManbooleanTrue for ironman modes, including group variants (isIronMan is an alias). SDK v50+
.isGroupIronman / .isGroupIronManbooleanTrue for GIM / HCGIM / UGIM (isGroupIronMan is an alias). SDK v50+
.localPlayerPlayer | nullThe local player entity
.invokeMenuAction(action)booleanDispatch 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.

MemberTypeDescription
.yaw()numberCamera yaw (0–2047)
.pitch()numberCamera pitch
.zoom()numberCamera zoom level
.posX()numberCamera X position in scene coords
.posY()numberCamera Y position in scene coords
.posZ()numberCamera Z position (height)
.snapshot()CameraSnapshotAll 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.

MethodReturnsDescription
.boosted(skill)numberCurrent (boosted/drained) level
.real(skill)numberBase level
.experience(skill)numberTotal 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.

MethodReturnsDescription
.isActive(prayer)booleanWhether 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.

MethodReturnsDescription
.varbit(id)numberRead a varbit value
.varp(id)numberRead 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.

MethodDescription
.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.

MethodReturnsDescription
.find(packedId)Widget | nullLook up a widget by packed ID
.children(parentPackedId)Widget[]Get all children of a parent widget
.findByText(query)Widget | nullFind first widget whose text contains query
.pack(group, child)numberCreate a packed widget ID from group + child
.interact(opcode, packedId, p0, p1)booleanClick a widget programmatically; returns whether queued
.setText(packedId, text)booleanReplace 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.

MethodReturnsDescription
.flag(plane, x, y)numberRaw collision flag at scene coordinates
.isBlocked(plane, x, y, dx, dy)booleanWhether 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.

MethodReturnsDescription
.item(id)ItemCompositionItem definition (name, noted, stackable, etc.)
.npc(id)NpcCompositionNPC definition (name, actions, combat level)
.obj(id)ObjectCompositionObject definition (name, actions, type)
.varbit(id)VarbitCompositionVarbit 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.

PropertyTypeDescription
.playersbooleanGet/set whether other players are hidden
.npcsbooleanGet/set whether NPCs are hidden
.selfbooleanGet/set whether local player is hidden
.scenebooleanGet/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.

MethodDescription
.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.

MethodReturns / 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.

MemberDescription
.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.

MethodDescription
.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:

MethodReturnsDescription
.first()Entity | nullFirst match, or null if none
.toArray()Entity[]All matches as an array
.count()numberNumber of matches
.any()booleanTrue if at least one match exists
.forEach(fn)voidIterate all matches with a callback

titan.queries.npcs()

Search for NPCs in the loaded scene.

FilterDescription
.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.

FilterDescription
.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.

FilterDescription
.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.

FilterDescription
.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.

FilterDescription
.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
.totalQuantitySum of all matched item quantities (property)
.existsTrue 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.).

FilterDescription
.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.

MemberTypeDescription
.isOpenbooleanWhether the inventory tab is visible
.sizenumberNumber of occupied slots
.emptySlotsnumberNumber of free slots
.isFullbooleanTrue if all 28 slots are occupied
.isEmptybooleanTrue if inventory is empty
.getAll()Item[]All items in the inventory
.find(idOrName)Item | nullFind item by ID or name
.getSlot(index)Item | nullItem at specific slot index
.contains(idOrName)booleanWhether the item exists
.count(idOrName)numberQuantity of the item
.containsAny(ids)booleanTrue if any of the IDs are present
.containsAll(ids)booleanTrue if all IDs are present
.drop(idOrName)voidDrop 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.

MemberTypeDescription
.inDialoguebooleanWhether a dialogue box is open
.continueDialogue()voidClick "Click here to continue"
.continueMake()voidClick "Make" on a Make-X interface
.hasOption(text)booleanCheck if a dialogue option exists
.selectOption(texts)voidSelect first matching option from array
.handleDialogue(choices)voidAuto-continue or select from choices array
.isQuestCompletionOpenbooleanWhether the quest completion scroll is shown
.closeQuestCompletion()voidDismiss 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.

MemberTypeDescription
.getSpecialAttackPercentage()numberCurrent spec energy (0–100)
.isSpecialAttackEnabled()booleanWhether spec is toggled on
.isAutoRetaliateEnabled()booleanWhether auto-retaliate is on
.enableSpecialAttack()voidToggle special attack on
.setAutoRetaliate(enabled)voidSet 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.

MemberTypeDescription
.getAll()Item[]All equipped items
.find(slot)Item | nullGet item in an equipment slot
.contains(idOrName)booleanWhether the item is equipped
.count(idOrName)numberQuantity equipped (for ammo, etc.)
.unequip(slot)voidRemove 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.

MemberTypeDescription
.isOpenbooleanWhether the bank interface is open
.getAll()Item[]All items in the bank
.contains(idOrName)booleanWhether bank contains the item
.count(idOrName)numberQuantity in bank
.find(idOrName)Item | nullFind an item in the bank
.close()voidClose the bank interface
.depositAll()voidDeposit entire inventory
.depositEquipment()voidDeposit all worn equipment
.depositAllOfItem(idOrName)voidDeposit all of a specific item
.withdrawItem(idOrName)voidWithdraw one of the item
.withdrawAll(idOrName)voidWithdraw all of the item
.withdrawAmount(idOrName, qty)voidWithdraw a specific quantity
.isNearBank()booleanWhether player is near a bank
.open()voidOpen the nearest bank
.isPinVisible()booleanWhether the bank PIN interface is shown
.typePin(pin)voidEnter 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.

MethodDescription
.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.

MethodDescription
.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

MethodDescription
.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

MethodDescription
.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

MethodDescription
.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

MethodReturnsDescription
.worldToScreen(worldX, worldY, worldZ)ScreenPoint | nullWorld coordinates to screen (worldZ is plane)
.tileToScreen(tileX, tileZ, plane, heightOffset?)ScreenPoint | nullScene 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).

MemberTypeDescription
ActorBase: hashIndex, tileX, tileY, plane, worldX, worldY, preciseX, preciseY, orientation, animation, interactingIndex, interactingType, interactingPhase, entityPtr, tile, worldPoint, distanceTo, interacting()Actor | null
.namestringNPC name
.idnumberNPC composition id
.overrideTransformnumberTransform override index from definition pipeline
.sizeX / .sizeYnumberFootprint in tiles
.isAnimatingbooleananimation !== -1
.actionsstring[]Cached right-click actions
.overheadIconnumberCache-default head icon ordinal (< 0 = none)
.hasHeadIconOverridebooleanRuntime head-icon override
.healthRationumberHeadbar fill, or -1 when none (SDK v48+)
.healthScalenumberHeadbar max, or -1 when none (SDK v48+)
.hasHealthBarbooleanAny active health bar (SDK v48+)
.hasAction(action)booleanSubstring match on actions
.interact(action)booleanQueue NPC menu entry
.isOverheadActive(icon?)booleanOverhead icon predicate
.healthPercent()numberHealth in [0,1] or -1 (SDK v48+)
.isDead()booleanHealth 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.

MemberTypeDescription
ActorBase: same positional / interaction fields as NPC (see titan-plugin-sdk.d.ts)
.namestringDisplay name
.combatLevelnumberCombat level
.isHiddenbooleanHidden by filters
.isStationarybooleanNo pending movement
.isAnimatingbooleanAnimation active
.isIdlebooleanStationary and not animating
.overheadIconnumberPrayer icon ordinal (< 0 = none)
.skullIconnumberSkull icon ordinal (< 0 = none)
.healthRationumberHeadbar fill, or -1 when none (SDK v48+)
.healthScalenumberHeadbar max, or -1 when none (SDK v48+)
.hasHealthBarbooleanAny active health bar (SDK v48+)
.isOverheadActive(icon?)booleanPrayer icon predicate
.isSkulled()booleanWilderness skull visible
.healthPercent()numberHealth in [0,1] or -1 (SDK v48+)
.isDead()booleanHealth bar active and ratio at zero (SDK v48+)
.interacting()Actor | nullInteraction 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().

MemberTypeDescription
.namestringObject name
.typestringClassification string (same role as C++ typeName())
.idnumberLoc definition id
.sizeX / .sizeYnumberFootprint
.layernumberWall / Decor / Scenery / GroundDecor (or -1)
.tileTileScene tile (x, y, plane)
.actionsstring[]Right-click actions
.hasAction(action)booleanWhether a right-click option exists
.interact(action)booleanQueue 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().

MemberTypeDescription
.namestringItem name
.idnumberItem id
.quantitynumberStack size
.ownershipTypenumberRaw ClientObj ownership; compare to titan.GroundItemOwnership (unknown reads as 0xFFFFFFFF). SDK v50+
.canLoot()booleanWhether this drop is lootable for the current account. SDK v50+
.tileTileLocation on the ground
.interact(action)booleanQueue ground-item action

Item (Inventory/Equipment)

Returned by inventory and equipment queries or utils.

MemberTypeDescription
.idnumberItem ID
.namestringItem name
.quantitynumberStack size
.slotnumberInventory slot index
.interact(action)booleanQueue inventory action
.useOn(target)booleanUse 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().

MemberTypeDescription
.planenumberMap plane
.startX / .startZnumberStart position (engine space)
.targetX / .targetZnumberTarget position
.sourceEntity / .targetEntitynumberEncoded entity ids from client
.spotAnimIdnumberGraphics id
.startTick / .endTicknumberLifecycle ticks
.sceneX / .sceneY / .sceneZnumberFine scene coordinates
.yaw / .pitchnumberOrientation
.hasMovedbooleanInterpolation / 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

ConstantConstantConstant
Skill.AttackSkill.HitpointsSkill.Mining
Skill.StrengthSkill.AgilitySkill.Smithing
Skill.DefenceSkill.HerbloreSkill.Fishing
Skill.RangedSkill.ThievingSkill.Cooking
Skill.PrayerSkill.CraftingSkill.Firemaking
Skill.MagicSkill.FletchingSkill.Woodcutting
Skill.RunecraftSkill.SlayerSkill.Farming
Skill.ConstructionSkill.HunterSkill.Overall

Prayer

Ordinal values match _titan.Prayer in the JS bootstrap (UPPER_SNAKE_CASE keys).

ConstantConstantConstant
Prayer.THICK_SKINPrayer.PROTECT_FROM_MAGICPrayer.PIETY
Prayer.BURST_OF_STRENGTHPrayer.PROTECT_FROM_MISSILESPrayer.RIGOUR
Prayer.CLARITY_OF_THOUGHTPrayer.PROTECT_FROM_MELEEPrayer.AUGURY
Prayer.SHARP_EYEPrayer.EAGLE_EYEPrayer.SMITE
Prayer.MYSTIC_WILLPrayer.MYSTIC_MIGHTPrayer.REDEMPTION
Prayer.ROCK_SKINPrayer.STEEL_SKINPrayer.CHIVALRY

HeadIcon

Overhead prayer/skull icons visible above players.

ConstantDescription
HeadIcon.MELEEProtect from Melee icon
HeadIcon.RANGEDProtect from Missiles icon
HeadIcon.MAGICProtect from Magic icon
HeadIcon.RETRIBUTIONRetribution icon
HeadIcon.SMITESmite icon
HeadIcon.REDEMPTIONRedemption icon
HeadIcon.SKULLPvP skull

EquipmentSlot

ConstantConstantConstant
EquipmentSlot.HeadEquipmentSlot.BodyEquipmentSlot.Ammo
EquipmentSlot.CapeEquipmentSlot.LegsEquipmentSlot.Ring
EquipmentSlot.AmuletEquipmentSlot.HandsEquipmentSlot.Shield
EquipmentSlot.WeaponEquipmentSlot.Feet 

InventoryID

Container IDs for use with titan.state.itemContainer().

ConstantDescription
InventoryID.INVENTORYPlayer inventory (28 slots)
InventoryID.EQUIPMENTWorn equipment
InventoryID.BANKBank storage
InventoryID.TRADETrade window

MenuAction

Opcodes for programmatic menu actions via invokeMenuAction or widget interaction.

ConstantDescription
MenuAction.NPC_FIRST_OPTIONFirst NPC right-click option
MenuAction.NPC_SECOND_OPTIONSecond NPC option
MenuAction.NPC_THIRD_OPTIONThird NPC option
MenuAction.NPC_FOURTH_OPTIONFourth NPC option
MenuAction.NPC_FIFTH_OPTIONFifth NPC option
MenuAction.GAME_OBJECT_FIRST_OPTIONFirst object option
MenuAction.GAME_OBJECT_SECOND_OPTIONSecond object option
MenuAction.GROUND_ITEM_FIRST_OPTIONFirst ground item option
MenuAction.WALKWalk-here action
MenuAction.CC_OPWidget click/interact
MenuAction.CC_OP_LOW_PRIORITYLow-priority widget click
MenuAction.WIDGET_TARGETUse item/spell on target
MenuAction.PLAYER_FIRST_OPTIONFirst player option (e.g. Follow)
MenuAction.ITEM_USEUse item

OverlayLayer

Controls draw order for overlay elements.

ConstantDescription
OverlayLayer.AboveSceneDrawn above the 3D scene, below widgets
OverlayLayer.AboveWidgetsDrawn 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.

MethodReturnsDescription
.all()PluginHandle[]All registered plugins
.get(id)PluginHandleGet plugin by ID (throws if not found)
.find(id)PluginHandle | nullFind plugin by ID (null if not found)
.selfPluginHandleHandle to the current plugin

A PluginHandle has the following members:

MemberTypeDescription
.idstringPlugin ID
.namestringDisplay name
.isEnabled()booleanWhether the plugin is currently enabled
.enable()voidEnable the plugin
.disable()voidDisable the plugin
.toggle()voidToggle enabled state
.setEnabled(v)voidSet 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 onClientTick or onRender.
  • 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.