Skip to content

Playing with mods ​

Everything happens at tspml.vercel.app. No installs, no accounts.

First load ​

  1. Open the portal. A boot overlay walks through four steps: service worker, mixin plan, game, mods. A progress bar tracks the steps, and a live tail of the boot log shows what's happening during the slower ones.
  2. The real PolyTrack loads inside the page (fetched live from the official servers through the portal's proxy, transformed on the way).
  3. Play. Your save data, tracks, and records live in your browser exactly like on the official site.

Slow first boot?

The very first load does the heavy lifting (registering a service worker and transforming the game bundle). Later loads are much faster: the portal pre-warms the transformed bundle and reuses it, so a warm boot takes a fraction of a second where a cold one takes a few.

The layout ​

The game fills the whole page. There is no split screen: mod management lives in the Mods menu, opened from the button over the game (Esc or its Close button puts it away), and the catalog lives in the Browse drawer beside it.

  • Stage: the game, edge to edge. The Expand button grows it over the whole tab (Esc or Shrink exits); the Fullscreen button uses real browser fullscreen.
  • Mods menu: the mod manager. Live event counters, your mods, mixin reports, and a session log, as an overlay you open when you want them and close when you want the road back.

Adding a mod ​

Open the Mods menu over the game, then click Add a mod (the shelf's primary button — it opens a popup). There are three ways in:

Paste a mod ​

Four boxes, two of them required:

BoxRequiredWhat goes in
mod.jsonyesThe manifest, verbatim
entrypoint.jsyesThe mod's built JavaScript (not TypeScript source)
mixins.jsonoptionalOnly if the mod declares mixins
physics.jsonoptionalOnly if the mod declares physics

Click Add mod. The mod validates, loads, and appears under Your mods.

If a mod's manifest declares mixins or physics and you leave that box empty, the mod still loads — but the panel says so explicitly, naming the mod and the missing file. A physics patch that quietly did nothing would read as "the mod doesn't work"; TSPML would rather tell you which file is missing.

Import from a URL ​

Pick the From a URL card and paste a link to either:

  • a mod.json: the entrypoint (and any mixin configs) are fetched relative to it, or
  • a bare .js file: TSPML synthesizes a minimal manifest around it.

The fetch happens directly from your browser, so the host needs to allow cross-origin requests (raw GitHub links work well).

Pointing at a PolyModLoader mod

If the URL turns out to be a PML mod (its manifest has a polymod key), the import is refused with that stated as the reason, rather than failing with a confusing complaint about a missing TSPML field. TSPML can't run PML mods yet; the intent and its limits are written up in PML compatibility.

Import a modpack ​

A modpack is a plain text file. One mod URL per line, # for comments:

# rewis's racing setup
https://raw.githubusercontent.com/someone/hud-mod/main/mod.json
https://raw.githubusercontent.com/someone/sound-pack/main/dist/index.js

Pick the A modpack card, then either paste those lines into the box or paste a single link to the .txt file. One .txt link means "fetch this list"; anything else means "the box is the list".

Each line is an ordinary URL import, so everything above applies per mod. What that buys you:

  • A bad line doesn't take the pack down. A line that 404s, points at a refused host, or isn't a URL at all is named in the Log with its line number and skipped. The rest still install, and the box reports the count: installed 2 of 3, 1 failed to import.
  • Re-importing is safe. A mod already installed is replaced, not duplicated, so running a pack again converges on the same set.
  • Fetched lists can use relative lines. In a .txt the portal fetched, mods/turbo/mod.json resolves next to the list itself, so a pack keeps working when it's forked or moved to another host. A pack you pasted has nothing to resolve against, so its lines must be full URLs.
  • Up to 16 mods per pack. Anything past that is dropped and counted.
  • A pack can't include another pack. A .txt line inside a list is refused rather than followed, so what you're about to install is always the list in front of you.

There's a working example at tspml.vercel.app/sample-pack.txt — paste that URL into the box to see the whole path run.

Mods are code

A mod runs unsandboxed in the page, with the same access as the portal itself. Only add mods you wrote or trust. A malicious mod can do anything your browser tab can do. This goes double for a modpack: importing one is trusting every author in the list, not just whoever wrote the list.

Your mods: reading a mod card ​

Each mod under Your mods renders as a card:

  • A tile with the mod's icon, when its mod.json sets one (an image URL, a path next to the manifest, or an inline data:image URI; see the icon). Without one, the tile shows the first letter of the mod's id.
  • The id and a status pill (enabled or disabled).
  • A facts line: the mod's version and how many mixins it declares, when it has either.
  • An origin line: where the mod came from. URL-imported mods show a link to their source (truncated to one line; hover for the full URL, or click to open it). Pasted mods say so plainly: "pasted (this browser only)", because their only copy is your browser's storage.
  • The action buttons:
    • source shows the mod's exact stored contents: the mod.json, the entrypoint code, and the mixins if any. What you see is what runs. Useful for checking what a URL import actually fetched before trusting it further.
    • disable / enable toggles the mod. Disabling unloads it immediately (its cleanup runs); enabling loads it again.
    • remove deletes the stored mod entirely.
    • docs appears when the mod's mod.json declares a docs URL: it opens the mod's own usage documentation in a new tab. This is a dedicated field, not the repo link, so the button always lands on instructions a player can read.
    • site appears when the mod declares a homepage: the mod's repository or project page.
    • Only plain http(s) URLs are shown for either link; anything else in those fields is ignored.

Mods persist in your browser's localStorage, so they come back on every visit. Re-adding a mod with the same id replaces the stored copy; that's the iteration loop modders use.

Reloading mods ​

The reload button in the Your mods header (shown once you have at least one mod) does two things in one click:

  1. Re-fetches every URL-imported mod from its source. This is the iteration loop for hosted mods: the author pushes a new build, you click reload, you play it. Each re-fetch goes through the same importer (same host rules, same size caps) and deliberately bypasses the browser's HTTP cache so you get the current file, not a stale copy. If a source is unreachable, the portal keeps your stored copy and says so; the rest still reload.

  2. Reloads the whole mod set, pasted mods included, through a full unload/load cycle, so disposers run and entrypoint changes apply live. Mixin changes raise the restart banner as usual.

GitHub raw links and freshness

raw.githubusercontent.com sits behind a CDN that caches files for about five minutes and ignores cache-busting. A reload within that window can still return the previous build. Wait a few minutes and reload again, or host on a service without that cache when iterating quickly.

Sharing your mod setup ​

The share button in the Your mods header builds a link that reproduces your current setup on someone else's machine.

The link is the portal URL with one mods= parameter per mod, each pointing at the mod's source URL. Three rules govern it:

  • Links only, never code. Only URL-imported mods can ride a share link. Pasted mods exist only in your browser, and the panel names them so you can send them another way instead of discovering the gap later.
  • Enabled mods only. The link reproduces what you're actually running, not your archive of toggled-off experiments.
  • Capped at 16 mods per link, to keep the URL and the recipient's confirmation list sane.

The share panel ​

Clicking share opens a panel showing the full generated link in a selectable box, with a copy button next to it. Copying is an explicit action rather than automatic, because clipboard writes can fail silently in some browsers; the visible link is the ground truth, and if the copy button fails you can select the text by hand. The panel closes on its own if you change your mod set, so it never shows a stale link.

A share link never auto-installs anything. The recipient sees a confirmation panel listing every mod URL the link carries, with a plain reminder that mod code runs unsandboxed. Nothing is fetched or run until they click Import; dismiss throws the list away. Links that fail the import rules (wrong host, bad shape) are shown as refused with the reason, so a doctored URL fails loudly instead of silently shrinking the list.

After a confirmed import, each mod goes through the normal URL importer, with all its usual rules.

Mixins and the restart banner ​

A mod's entrypoint (events, keybinds, tracks, audio) applies live. Its mixins, deep patches to the game code, apply when the game bundle is served. So after adding, changing, or disabling a mod with mixins you'll see a restart banner. Click reload now; the game reloads with the new patch set.

After the reload, the Your mixins section reports exactly what happened, per mod: 1/1 applied, or a failure reason (an unknown symbol, a stale mapping, a patch collision). A failing mixin only fails that mod's row. It can never take down the game, the mod layer, or other mods.

Reading the status rows ​

RowMeaning
bridge: car.control × NLive proof the mod layer sees game events
mods: ✓ some-mod-idWhich mods loaded (and which failed, with reasons)
safety: …Warn-only fairness labels; see Safety & fairness
Your mixinsPer-mod mixin apply report after a reload
LogTimestamped session log (collapsed by default)

The log records the whole session: boot steps, mod loads and failures, imports, reloads, and share actions. When something behaves oddly, it's the first place to look.

A note on leaderboards ​

PolyTrack validates leaderboard runs as deterministic input replays, and Kodub has said server-side anti-cheat is in development. A mod that changes physics or timing can invalidate your runs, and uploading them risks leaderboard bans. TSPML labels such mods but does not block you; the risk is yours. Cosmetic mods (HUDs, sounds, keybinds) don't touch the sim.

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