#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:

  1. index.html at the top of outputDir. Most engines let you name the export file; name it index.html. Renaming it afterwards usually breaks the engine's own references, so set it in the export dialog.
  2. Everything relative. Your game is served from /games/<slug>/, not the site root, so an absolute /game.wasm will 404. Engines get this right by default; hand-edited HTML often doesn't.
  3. No server. The arcade serves static files. Anything needing a process of its own belongs in a backend module instead.
  4. 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" in game.json: the bar parks off-screen behind a small handle, and --arcade-nav-h shrinks to the handle's height. See the arcade nav bar.

  • The bar floats over your canvas, because a canvas sized to 100vh won't make room for anything in normal flow. The arcade reserves the strip for you — every game page gets padding-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.Arcade exists 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

View docs/engines/web-export.md on GitHub ↗