Skip to content

Your first mod

This guide takes you from nothing to a working mod running inside PolyTrack. No prior modding experience needed. If you can write a little JavaScript, you can write a mod.

The shortest possible mod

A TSPML mod is, at minimum, two pasted files. Here's a complete, working mod you can try right now at tspml.vercel.app:

mod.json

json
{
  "schemaVersion": 1,
  "id": "hello-polytrack",
  "name": "Hello PolyTrack",
  "version": "1.0.0",
  "entrypoint": "entrypoint.js",
  "targets": [">=0.6.0 <0.7.0"]
}

entrypoint.js

js
export default (api) => {
  api.events.on('checkpoint.passed', ({ index, isReplay }) => {
    if (isReplay === true) return; // skip ghost cars
    api.logger.log(`[hello] checkpoint ${index} passed!`);
  });

  api.keybinds.register({
    id: 'hello-polytrack.greet',
    key: 'KeyH',
    description: 'Say hello',
    onDown: () => api.logger.log('[hello] Hello from my mod!'),
  });

  api.logger.log('[hello] loaded');

  // Return a disposer; called when the mod is disabled/removed.
  return () => api.logger.log('[hello] unloaded');
};

Open the portal, expand Add a mod, paste the two files into the first two boxes, click Add mod. The sidebar shows mods: ✓ hello-polytrack, checkpoints log as you race, and H greets you.

That's the whole loop. Everything below is refinement.

The entrypoint contract

Your entrypoint is an ES module whose default export is a factory:

js
export default (api) => {
  // set up: subscribe, register…
  return () => { /* tear down */ };
};

Three rules worth internalizing:

  1. Everything you register returns an undo. api.events.on(...) returns an unsubscribe function; api.keybinds.register(...) returns an unregister function. Call them in your disposer:

    js
    export default (api) => {
      const off = api.events.on('car.control', handler);
      const unbind = api.keybinds.register({ ... });
      return () => { off(); unbind(); };
    };
  2. Returning nothing is fine. Your mod is just reported as no-op on unload instead of unloaded.

  3. Throwing is contained. A mod that throws, whether at load, in an event handler, or during unload, is caught, reported in the sidebar, and never takes down the game or other mods.

Developing with TypeScript (optional)

If you prefer TypeScript and a real project folder, scaffold one from the TSPML repo:

bash
git clone https://github.com/roowus/TSPML.git
cd TSPML && pnpm install --ignore-scripts
node tooling/create-tspml-mod/bin/create-tspml-mod.mjs my-first-mod
cd ../my-first-mod && pnpm install && pnpm build

You get a typed src/entrypoint.ts (with a local TspmlApi type declaration), a starter mixins.json, and a self-contained tsconfig. pnpm build emits dist/src/entrypoint.js, and that built file is what you paste into the portal, not your TypeScript source.

Why paste the built file?

The portal imports your entrypoint as a real ES module via a Blob URL. Browsers execute JavaScript, not TypeScript, so the compiled output is the mod.

Things every mod should know

Ghost cars fire events too

race.started, checkpoint.passed, and race.finished fire once per car, and a ghost/replay car is a car. Every payload carries isReplay so you can tell them apart:

js
api.events.on('race.finished', ({ frames, isReplay }) => {
  if (isReplay === true) return; // a ghost finished, not the player
  showTime(frames);
});

Compare with === true, not truthiness: isReplay is boolean | null, and null means TSPML could not determine it. Treat that as unknown, never as "the player".

Times are frames

race.finished reports frames, not milliseconds. PolyTrack simulates at 60 fps: seconds = frames / 60.

Iterating on your mod

Two loops, depending on how you work:

  • Pasting: re-paste with the same id and click Add mod again. The stored copy is replaced, the old instance unloads, the new one loads. Change, rebuild, re-paste.
  • Hosting: import your mod from a URL once, then push new builds to the same URL and click the reload button in the Your mods header. The portal re-fetches from the source and reloads. See Loading your mod.

Next steps

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