Skip to content

Loading your mod

How mods get into the portal, what persists, and what the lifecycle looks like.

The ways in

Paste

The default method in Add a mod: three textareas for mod.json, the built entrypoint.js, and an optional mixins.json. This is the modder's iteration loop, since re-pasting with the same id upserts the stored copy.

Import from a URL

Switch the dropdown to Import from a URL. Two shapes work:

  • A mod.json URL. TSPML fetches the manifest, then fetches the entrypoint file relative to the manifest's URL, plus any mixin configs the manifest declares for the web host. Host a mod folder anywhere static (GitHub raw links, GitHub Pages, your own site) and share one link.
  • A bare .js URL. TSPML wraps it in a synthesized minimal manifest. A quick way to share a single-file mod.

Rules the importer enforces:

  • The fetch happens in your browser, directly, never through the portal's game proxy. The host must allow cross-origin requests (raw.githubusercontent.com does).
  • URLs pointing at the game's own servers or the portal's API are refused.
  • Size caps apply to everything fetched.

Mods imported from a URL keep their source URL: the mod card shows it on the origin line, the reload button re-fetches from it, and the share button can carry it in a link.

A third way in, for recipients: someone sends you a portal link with mods= parameters (built by the portal's own share button). Opening it shows a confirmation panel listing every mod URL; nothing is fetched or run until you confirm. Each confirmed link then goes through the URL importer above, with all the same rules. Details in Playing with mods.

Reloading

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: push a new build to your host, click reload, play it. Each re-fetch goes through the same importer (same host rules, same size caps) and bypasses the browser's HTTP cache, so it picks up the current file rather than a stale copy. If a source is unreachable, the portal keeps your stored copy and says so; the rest still reload. (One caveat: GitHub's raw CDN has its own cache of about five minutes that can't be bypassed from the browser.)
  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.

What persists

Added mods are stored in your browser's localStorage (key tspml.userMods.v1): manifest, code, mixins, source URL, and enabled state. They load automatically on every visit until you remove them. Nothing is uploaded anywhere; "your mods" means your browser's mods.

You can inspect any stored mod at any time with its card's source button, which shows the exact manifest, entrypoint code, and mixins that will run.

The lifecycle

add / importvalidate manifestload entrypointBlob-URL importfactory(api) runsany failure is isolated,reported in that mod's own rowdisposer captured — runs ondisable / remove / page close
  • Load order is a topological sort over declared dependencies, with cycle detection.
  • Failures are per-mod. A mod with a broken manifest, a throwing entrypoint, or a failing mixin is reported in its own row; every other mod loads normally.
  • Soft-disable (details in the manifest spec): a mod whose targets don't match the running game version, whose environment names a different host, or which breaks an installed mod is excluded and reported, never a boot abort.

Unload

A mod unloads when you disable it, remove it, or close the tab. In order:

  1. loader.onUnload is emitted while everything is still live; handlers can still call keybinds.unregister, tracks.unregister, etc.
  2. Each mod's disposer runs (reverse load order, isolated, so one mod throwing doesn't block the rest).
  3. The bridge's registries are disposed last.

Session-scoped registrations (keybinds always; tracks/audio unless you opted into persistence) are cleaned up automatically.

Entrypoints vs. mixins: when things apply

AppliesUndo
Entrypoint (events, keybinds, tracks, audio)Live, immediately on add/enableLive, immediately on disable/remove
Mixins (patches to game code)On the next game loadOn the next game load

The served game bundle is immutable once running, so any change to the mixin set (adding a mod with mixins, disabling one) raises the restart banner. Click reload now and the game comes back with the updated patch set, and the Your mixins report tells you exactly what applied.

Declared-but-missing mixins

If a mod.json declares "mixins": [...] but you didn't paste a mixins.json, the mod still loads, and the sidebar tells you the declared mixins were skipped, not silently ignored. Re-paste with the third box filled to clear it.

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