Skip to content

The api object

Your entrypoint's one argument. Everything a mod does goes through it.

ts
export interface TspmlApi {
  readonly events:   TspmlEventSubscriber; // subscribe-only event bus
  readonly keybinds: KeybindsRegistry;
  readonly tracks:   TracksRegistry;
  readonly audio:    AudioRegistry;
  readonly logger:   TspmlLogger;          // console-shaped
  readonly version:  string;               // the loader's semver
}

api.events: subscribe-only by design

js
const off = api.events.on('race.finished', handler);   // returns unsubscribe
api.events.once('track.afterLoad', handler);           // fires once
api.events.off('race.finished', handler);              // explicit removal

Two properties worth knowing:

  • Per-listener error isolation. A listener that throws is caught and logged; it never blocks other listeners or the game.
  • No emit. The type is a subscriber: mods observe events, they don't forge them. (The compile-time guarantee is exactly that, compile-time. Pasted runtime JavaScript answers to the trust model instead: a mod is code you chose to run.)

The full event list with payloads: Events.

api.keybinds, api.tracks, api.audio

The three implemented registries, each documented with examples in Registries. The shared conventions:

  • Registration returns an undo (a function, or a typed result). Call it in your disposer.
  • Failures are typed values, never exceptions: { ok: false, reason: '…' }, so your mod handles them like data.
  • Early calls are queued. Registering before the game finishes booting is fine; the bridge drains the queue once the game object it needs is captured.

api.logger

Console-shaped (log / warn / error), prefixed and routed into the portal's session log. Prefer it over console.log, because players can actually see it in the sidebar's Log section.

Lifecycle events

js
api.events.on('loader.onUnload', () => { /* observe teardown */ });

For your own cleanup, return a disposer from your factory (or implement onUnload in the class form) instead of subscribing. The loader calls it directly, in reverse load order, isolated per mod. The teardown order the host guarantees:

  1. loader.onUnload fires while the bus and registries are still live, so you can still call keybinds.unregister, tracks.unregister, etc.
  2. Mod disposers run (reverse load order).
  3. The bridge's registries are disposed last.

The portal triggers this on tab close, page navigation, and per-mod disable/remove.

TSPML is a fan-made tool. It never redistributes PolyTrack; the portal transforms your own live copy of the game.