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 removalTwo 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:
loader.onUnloadfires while the bus and registries are still live, so you can still callkeybinds.unregister,tracks.unregister, etc.- Mod disposers run (reverse load order).
- The bridge's registries are disposed last.
The portal triggers this on tab close, page navigation, and per-mod disable/remove.