Publishing & sharing your mod
There's no central mod store (yet). Sharing a TSPML mod today means hosting files somewhere public and sharing a link, which the portal's URL import turns into a one-click install. A modpack file or a share link turns a whole set of those links into a one-click setup.
The URL-import layout
Host your mod's files together, anywhere static:
my-mod/
mod.json # the manifest
entrypoint.js # the BUILT entrypoint (mod.json's "entrypoint" points here)
mixins.json # only if declared in mod.json's "mixins"
physics.json # only if declared in mod.json's "physics"The importer fetches mod.json, then resolves entrypoint, each mixin config, and physics relative to the manifest's URL. So one link, to mod.json, is the whole install instruction.
A physics.json is validated at import time, not at race time: if it doesn't parse or breaks a limit, the import fails with the reason while you're still the person who can fix it.
Hosting that works
The fetch happens directly from the player's browser, so the host must send CORS headers:
| Host | Works? |
|---|---|
raw.githubusercontent.com (a file in a public repo) | yes, the recommended default |
| GitHub Pages | yes |
| Most static hosts (Netlify, Vercel, Cloudflare Pages) | yes |
| A URL shortener or an HTML page ABOUT the file | no, the URL must serve the raw file |
A single-file mod is even simpler: share a link to the bare .js file and the importer synthesizes a manifest around it.
Updates reach players through the reload button
When you push a new build to the same URL, players don't have to re-import: the portal's reload button re-fetches every URL-imported mod from its source. The re-fetch bypasses the browser's HTTP cache; note that GitHub's raw CDN adds its own cache of about five minutes that can't be bypassed from the browser, so rapid iteration is smoother on hosts without that cache.
Share links: shipping a whole setup
Players can bundle your mod (and others) into a share link with the portal's share button: a portal URL carrying one mods= parameter per enabled URL-imported mod, capped at 16.
What this means for you as a publisher:
- The link carries URLs only, never code. Your hosting stays the distribution channel; the link is just pointers.
- Only URL-imported mods can ride a link. If you want your mod to be shareable, publish it at a URL rather than distributing files to paste.
- Recipients always see a confirmation list first. A share link never auto-installs; the recipient sees each mod URL and confirms before anything is fetched. Your mod's URL is its identity in that list, so a clear repo/path name helps people trust what they're importing.
Modpacks: shipping a set as a file
A modpack is a .txt file listing one mod URL per line, with # for comments. Players import it by pasting the lines or by pasting one link to the file. Publishing one takes no account and no upload step: put it in a gist, or next to your mods.
# turbo-pack.txt — my mods, plus two I like
mod.json
../sound-pack/mod.json
https://raw.githubusercontent.com/someone-else/hud/main/mod.jsonTwo things worth knowing when you author one:
- Lines may be relative to the pack file itself. That's what keeps a pack portable: fork it, move it to another host, and the relative lines still point at the right mods. (Relative lines only work in a pack the portal fetched. A pack a player pastes has no base URL to resolve against.)
- A pack can't include another pack. Lines ending in
.txtare refused, so what a player is about to install is always the list in front of them.
Same boundary as everything else here: a pack is links, never code, and the cap is 16 mods.
A publishing checklist
idis your namespace: lowercase[a-z0-9-], globally unique, stable across versions. Prefix your keybind ids and any globals with it.versionis semver: bump it on every release.targetsstates the game versions you tested, e.g.[">=0.6.0 <0.7.0"]. When PolyTrack ships a version outside your range, your mod is soft-disabled with a clear report instead of breaking mysteriously. That's a feature; keep the range honest.- Ship the built JS, not TypeScript. The manifest's
entrypointmust point at a real fetchable file. - Return a disposer and undo everything you set up. Players toggle mods, and a mod that leaks listeners on disable feels broken.
- Set
vanillaSafehonestly:falseif your mod touches physics or timing. It's a warn-only label, but it's the difference between an informed player and a banned one. - Re-derive
physics.jsonon a new game build. A physics plan pins the exact binary it was verified against, so a PolyTrack release makes yours stop applying — by design, and with a message saying why, rather than writing floats into a binary nobody checked. Re-runfind-constantagainst the new build, confirm the constant is still the one you meant, and ship the new pin. - A README helps: what the mod does, its keybinds, and its
mod.jsonURL for importing. - Remember the source button. Players can inspect your mod's exact fetched contents in the portal before and after trusting it. Readable built output is a courtesy that pays off.
Versioning and dependencies
mod.json supports relations between mods (depends, recommends, suggests, conflicts, breaks, provides) with npm semver ranges. The loader topologically sorts the load order and soft-disables what can't be satisfied. Details: mod.json manifest.
What's coming
A mod/modpack registry is on the roadmap (#80): short IDs that resolve to mod URLs, so a pack can be shared as a word rather than a link. The registry will store links only, never mod code; hosting stays wherever you publish. Share links and modpack files are the shipped slices of this design, and the portal's Add-form has the ID slot reserved next to them.