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.jsonURL. TSPML fetches the manifest, then fetches theentrypointfile 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
.jsURL. 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.
Share links
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:
- 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.)
- 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
- 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
targetsdon't match the running game version, whoseenvironmentnames a different host, or whichbreaksan 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:
loader.onUnloadis emitted while everything is still live; handlers can still callkeybinds.unregister,tracks.unregister, etc.- Each mod's disposer runs (reverse load order, isolated, so one mod throwing doesn't block the rest).
- 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
| Applies | Undo | |
|---|---|---|
| Entrypoint (events, keybinds, tracks, audio) | Live, immediately on add/enable | Live, immediately on disable/remove |
| Mixins (patches to game code) | On the next game load | On 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.