Docs
Hosting guide
How builds are validated, sandboxed and served — and how to package yours so it works the first time.
The short version
Zip the folder that contains index.html. Use relative asset paths. Don't load anything from a CDN.
That's about 90% of it. The rest of this page is the detail.
What happens when you upload
- Validation. We read the archive without extracting it and check the entry count, the uncompressed size, and the compression ratio. A ratio over 120:1 is rejected as a zip bomb. Any path containing
.., a leading slash, or a drive letter is rejected outright. - Executable screening. Anything with a native extension —
.exe,.dll,.so,.shand friends — fails the upload. Game Trough hosts web builds only. - Prefix stripping. Most zips nest everything under one folder. We detect that common prefix and strip it, so
MyGame/index.htmlandindex.htmlboth work. - Entry detection. We look for the entry candidates your stack declares, then for any root-level HTML file, then for any HTML file at all.
- Content addressing. The build is written to a path derived from a hash of the archive, which is why builds can be cached aggressively and why re-uploading the same bytes is idempotent.
How your game is served
Every file comes back from /api/play/<game-id>/… with this policy:
Content-Security-Policy:
default-src 'self' blob: data:;
script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:;
connect-src 'self' blob: data:;
frame-src 'none';
object-src 'none';
base-uri 'none';
form-action 'none'
unsafe-inline and unsafe-eval are there because game bundles inline their boot script and WASM glue needs eval. The important line is connect-src 'self' — a malicious build has nowhere to send anything it finds.
The iframe itself is sandboxed without allow-same-origin. That forces your game into an opaque origin: it cannot read the parent page, our cookies, or anyone's session, even though it's served from the same hostname. It's the single most important thing on this page.
What that means for you
- No CDN scripts.
<script src="https://unpkg.com/...">will be blocked. Vendor the library into your zip. - No remote assets. Fonts, textures, audio — bundle them.
- No external API calls. If your game needs to talk to a server, it needs the container runtime, which isn't switched on yet.
- localStorage works, but it's scoped to the opaque origin, so it's per-session rather than persistent. Don't rely on it for save data yet.
Per-stack packaging
Vanilla HTML/JS
Zip the folder. Done. Keep index.html at the top.
Vite / React / Three.js
Set the base path or every asset URL will be absolute and resolve to nothing:
// vite.config.js
export default { base: "./" }
Then npm run build and zip dist/.
Godot 4 (Web)
Export with the Web preset and zip the whole export folder — index.html, .wasm, .pck, .js. We set Cross-Origin-Embedder-Policy and Cross-Origin-Opener-Policy automatically so threads and SharedArrayBuffer work.
Unity WebGL
Use Decompression Fallback or disable compression entirely. Unity's default gzip/brotli export expects the server to set specific content encodings, and the fallback path avoids that whole class of problem.
Rust / wasm-bindgen
wasm-pack build --target web, then write a small index.html that imports the generated glue. Zip both.
Size limits
Limits are per-stack, listed on the stacks page. They're generous, but the honest advice is that anything over about 15 MB loses a meaningful number of players before the first frame. In order of impact: strip debug symbols, drop unused export templates, compress textures to WebP, and make sure something appears on screen within the first 500 ms.
Updating a build
Re-upload from your game's edit page. Each upload becomes a new version with its own changelog entry; the previous build stays in storage so a bad release can be rolled back.
Games that need a server
Python backends, socket servers, anything stateful — that's the container runtime. The abstraction is implemented end to end (routing, per-game subdomains, admin surface) but no provider is connected, so those stacks are marked planned and hidden from the submit form. When it's switched on, existing submissions get picked up without any migration.
Still broken?
Open the browser console on your game page — a white screen is almost always a 404 on an asset with an absolute path. If that isn't it, post the console output in Stack Help.