#Shipping an engine export
Godot, Unity, GameMaker, Defold, Construct: anything that exports a web build can go on the arcade. You commit the export, not the project, and CI never needs your engine installed, because the export is the artifact.
This page is what every engine has in common. The per-engine pages cover the
bridge to Arcade: Godot · Unity.
#The contract
// games/my-game/game.json
"build": {
"command": null, // nothing to run; the export is committed
"outputDir": "public" // where you exported to
}
Four rules, all enforced at build time except the last:
index.htmlat the top ofoutputDir. Most engines let you name the export file; name itindex.html. Renaming it afterwards usually breaks the engine's own references, so set it in the export dialog.- Everything relative. Your game is served from
/games/<slug>/, not the site root, so an absolute/game.wasmwill 404. Engines get this right by default; hand-edited HTML often doesn't. - No server. The arcade serves static files. Anything needing a process of its own belongs in a backend module instead.
- Commit the export. It's build output, but it's also the only copy CI sees.
#What the arcade adds to your export
At build time the arcade rewrites every .html in your output to inject the
shared top bar: a stylesheet link in <head>, a mount point at the top of
<body>, and the SDK plus nav.js before </body>. You don't add any of that
yourself, and it's skipped if you already have it.
Two consequences worth planning for:
The bar can be collapsed. A full-viewport export usually wants
"navBar": "collapsed"ingame.json: the bar parks off-screen behind a small handle, and--arcade-nav-hshrinks to the handle's height. See the arcade nav bar.The bar floats over your canvas, because a canvas sized to
100vhwon't make room for anything in normal flow. The arcade reserves the strip for you — every game page getspadding-top: var(--arcade-nav-h)on<body>— so ordinary page flow already clears it. What it can't fix is an element sized to the viewport: subtract the strip, or you get a scrollbar and a canvas running under the bar.canvas { height: calc(100vh - var(--arcade-nav-h, 0px)); }See the arcade nav bar.
window.Arcadeexists by the time your game runs, which is what makes the engine bridges on the next pages work.
#Compression and threads
Two engine defaults that the arcade's hosting can't satisfy. Both produce a game that runs locally and fails when deployed, so they're worth getting right first time.
Don't ship pre-compressed files. The deploy is a plain aws s3 sync and sets
no Content-Encoding, so a .wasm.br or .js.gz arrives as bytes the browser
won't decompress. Turn the engine's compression off, or enable its decompression
fallback so the engine handles it in JS. CloudFront compresses on the wire anyway,
so you lose very little.
Don't ship a threaded build. WebAssembly threads need SharedArrayBuffer,
which needs cross-origin isolation (COOP/COEP response headers). The arcade
doesn't set them, so a threaded export fails to boot. Use the single-threaded
export option. If you genuinely need threads, that's an infrastructure change to
the CloudFront config
and a conversation, not something to work around in the game.
#Sizes
Engine exports are large, and they land in a git repo everyone clones. A 5MB export is unremarkable; 100MB is a problem for everyone else's checkout. Strip what you don't ship: debug symbols, unused locales, source maps, uncompressed source art.
#Check it locally
npm run build && npm run preview
Play it at http://localhost:8002/games/<your-slug>/. The preview server mimics
production routing and serves .wasm as application/wasm, so if it loads here
it loads deployed.
Then run the same checks CI does:
npm run validate
See also: adding a game · submitting your game · troubleshooting