Extrascripts
Extrascripts are optional content modules that work independently of which gamemode is running. They're for universal functionality — things like debug tools, admin commands, or content that should exist on any server regardless of gamemode.
Extrascripts live in server/extrascripts/{id}/ and export a register function.
Creating an Extrascript
1. Create the directory
server/extrascripts/my-script/
index.ts2. Export a register function
// server/extrascripts/my-script/index.ts
import type { IScriptRegistry, ScriptServices } from "../../src/game/scripts/types";
export function register(registry: IScriptRegistry, services: ScriptServices): void {
registry.registerCommand("hello", (event) => {
services.messaging.sendGameMessage(event.player, "Hello from my extrascript!");
});
}That's it. Drop a folder in server/extrascripts/ with an index.ts exporting register, and it loads automatically at startup. No configuration needed.
What extrascripts can do
Extrascripts have access to the same IScriptRegistry and ScriptServices as gamemodes. They can register:
- Commands —
::mycommandchat commands - NPC interactions — talk-to, attack, pickpocket, etc.
- Loc interactions — object click handlers
- Item actions — inventory item options, item-on-item, item-on-loc
- Widget buttons — UI button click handlers
- Region events — enter/leave region triggers
- Tick handlers — per-tick logic
Load Order
- Gamemode calls
registerHandlers()first - All extrascripts call
register()after
Extrascript handlers run alongside gamemode handlers. If both register a handler for the same interaction, both will be evaluated — the registry determines priority.
Hot Reload
Extrascripts support hot-reload during development. Set the SCRIPT_HOT_RELOAD=1 environment variable and the server will watch for file changes, reloading extrascripts without a full restart.
Gamemodes vs Extrascripts
| Use case | System |
|---|---|
| Server-specific rules (XP rates, tutorials, progression) | Gamemode |
| Universal tools (debug commands, admin utilities) | Extrascript |
| Content that only matters for one server type | Gamemode registerHandlers() |
| Content that should work on any server | Extrascript |
Rule of thumb: if it makes sense on every server, it's an extrascript. If it defines or changes how the server plays, it's a gamemode.
Custom Content
Both gamemodes and extrascripts can define custom items and custom widgets using the built-in registries. These are automatically sent to the client at login and resolved seamlessly alongside cache data.
Custom Items
Use CustomItemBuilder and CustomItemRegistry to define items that don't exist in the OSRS cache. Custom items use IDs starting at 50000+ to avoid conflicts.
import { CustomItemBuilder } from "../../src/custom/items/CustomItemBuilder";
import { CustomItemRegistry } from "../../src/custom/items/CustomItemRegistry";
CustomItemRegistry.register(
CustomItemBuilder.create(50100)
.basedOn(3834) // clone properties from an existing cache item
.name("My Custom Item")
.inventoryActions("Activate", null, null, null, "Drop")
.build(),
"my-source-id",
);The client resolves custom items via CustomObjTypeLoader, which wraps the base cache loader. No client-side changes needed — registered items appear in inventories, shops, and tooltips like any cache item.
Custom Widgets
Use CustomWidgetRegistry to define widget groups that don't exist in the cache:
import { CustomWidgetRegistry } from "../../src/game/scripts/CustomWidgetRegistry";
const widgetGroup = buildMyWidgetGroup(); // your widget definition
CustomWidgetRegistry.register(widgetGroup);Custom widgets are serialized into the gamemode content data packet and delivered to the client at login.
Content Data Packet
Gamemodes can send arbitrary data to the client by implementing getContentDataPacket(). This is how custom items, widgets, and gamemode-specific data (like league tasks) reach the client. The core engine handles packet delivery at login — gamemodes just build the payload.
Bundled Extrascripts
| Extrascript | Description |
|---|---|
item-spawner | Admin debug tool — custom widget for searching and spawning items. Uses CustomItemBuilder and CustomWidgetRegistry for custom UI. Command: ::itemspawner |
Skill Implementations
Skills are owned by the vanilla gamemode and registered via its registerHandlers() call. Gamemodes that extend vanilla (such as Leagues V) inherit all skill handlers through super.registerHandlers().
Skills register their own action handlers via registerActionHandler, making them fully self-contained — the engine has no hardcoded knowledge of any skill's logic.
server/gamemodes/vanilla/skills/
├── index.ts # Aggregates sub-module register calls
├── consumables/ # Food eating, potion drinking
├── crafting/ # Flax picking, spinning wheel
├── firemaking/ # Fire lighting
├── fishing/ # Fishing spots, minnow exchange
├── fletching/ # Log cutting, bow stringing, arrow/bolt combining
├── herblore/ # Herb cleaning, potions, stamina
├── mining/ # Rock mining
├── prayer/ # Bone burying, ash scattering, altar offering
├── production/ # Cooking, tanning, bolt enchanting
├── sailing/ # Sailing, pandemonium
├── smithing/ # Smithing, smelting
├── thieving/ # NPC pickpocketing, lock picking
└── woodcutting/ # Tree chopping