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 Paste files card in Add a mod (the default): four textareas for mod.json, the built entrypoint.js, an optional mixins.json, and an optional physics.json. This is the modder's iteration loop, since re-pasting with the same id upserts the stored copy.

Declaring mixins or physics in the manifest and leaving the matching box empty is allowed, and reported: the mod loads and the panel names the missing file. Changing either one raises the restart banner, since both are applied to the game before it boots.

Import from a URL ​

Pick the From a URL card. 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.

Import a modpack ​

A .txt file, one mod URL per line, # for comments. Pick the A modpack card and either paste the lines or paste one link to the file.

This is sugar over the URL import and nothing more: each line goes through the importer above, so the same host rules, size caps and error messages apply per mod. What the pack layer adds is failure isolation across lines (a dead line is named and skipped, the rest install), dedupe, a 16-mod cap, and relative-line resolution against the list's own URL when the list was fetched.

Distributing a mod set this way keeps the same boundary share links have: a pack carries links, never code. Nothing about publishing a pack requires uploading a mod anywhere, and TSPML never becomes a host for one. Full player-facing details in Playing with mods.

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 Mods menu 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.