Shipping a Godot Game to the Browser With a Branded Loader
How I export a Godot 4 game to HTML5, host the build on object storage, embed it on my site, and replace the default Godot loading logo with my own. The practical web-shipping checklist I wish I had.
I build games on nights and weekends, and the moment that always trips me up is not the gameplay. It is the last mile. You have a finished Godot project sitting on your machine, it runs perfectly in the editor, and then you want a stranger to click a link and play it in their browser thirty seconds later. That gap is full of small papercuts nobody warns you about.
This is the checklist I wish I had when I shipped World 11, my World Cup draft roguelike where you spin up a nation and year, draft eleven real players, and sim a fourteen match gauntlet. By the end you will know how to export to HTML5, host the build cheaply, embed it without bloating your page, and replace that default Godot loading logo with your own art. Let me walk through it the way I actually did it.
Why The Browser At All
The web build is the cheapest possible front door. There is no install, no app store review, no platform account. Someone reads a post, clicks Play, and they are in. For a small game that you are sharing yourself, the friction of a download is the difference between a hundred people trying it and ten. So even though the desktop export is technically nicer, the browser version is the one that earns you players. I treat it as the demo that lives on my site, and everything below is in service of making that demo painless to ship.
Setting Up The Web Export Preset
In Godot 4 you open Project, then Export, and add a Web preset. If the preset shows a warning about missing export templates, you install them from the Editor menu under Manage Export Templates. With the preset selected you pick an output path and hit Export Project. Godot writes out a handful of files, and the ones that matter are index.html, a .js loader, the WebAssembly binary as a .wasm, the packed game data as a .pck, and a couple of small support files. The HTML file is the entry point, the wasm is the engine compiled for the browser, and the pck is your actual game. All of them need to live together at the same URL path or the loader cannot find them.
One habit that saved me grief: export into a clean, empty folder every time. The web export emits several files and it is easy to lose track of which ones are current when they are mixed in with old builds.
The Threads Decision That Changes Everything
Here is the single most important fork in the road, and I wish someone had put it in bold for me. The Web preset has a thread_support option. If you leave threads on, the browser requires the page to be in a cross-origin isolated context, which means your server has to send two specific response headers, Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy. If those headers are missing, the game simply will not boot, and the error is cryptic enough that you can lose an evening to it.
If you turn thread_support off, none of that applies. The build runs on a single thread, it does not need cross-origin isolation, and it does not need any special headers. That means a plain static host works with zero configuration. For a small game like World 11 the single threaded build runs perfectly well, so I turned threads off and never thought about COOP or COEP again. Unless you have a specific reason to need worker threads, this is the pragmatic choice, and it unlocks the cheap hosting in the next section.
Hosting The Build On Object Storage
Once threads are off, your build is just a pile of static files, and the cheapest way to serve static files is an object storage bucket fronted by a CDN. I uploaded the exported folder into an S3 compatible bucket and made it publicly readable, then pointed a CDN URL at it. No server, no container, no runtime to babysit. The build sits there and the CDN serves it close to whoever is playing.
The one thing that bites people is content types. The browser streams and compiles the WebAssembly as it downloads, but that streaming compilation only happens when the file is served with the application/wasm content type. If your storage serves the wasm as application/octet-stream, which many do by default, the browser falls back to a slower path and you may see warnings in the console. So after uploading I set the metadata explicitly, the wasm gets application/wasm, the html gets text/html, the js gets text/javascript, and the pck can stay as a generic binary type. Setting the wasm type correctly is the one that materially speeds up the load.
Embedding It With A Lazy Iframe
The honest truth about a web game is that it is a heavy download. You do not want to force every visitor to pull megabytes of wasm and game data just because they opened the page. So I do not embed the game directly. Instead I render a lightweight placeholder, a cover image with a Play button, and the actual <iframe> pointing at the CDN URL only gets mounted into the page after someone clicks Play.
That single decision means the page itself stays fast and light for everyone, and the big download is opt in. The people who actually want to play pay the cost, and the people who are just reading do not. When the click happens I swap the placeholder for the iframe, set its source to the hosted index.html, and let Godot take over inside the frame. It feels instant from the reader's side because nothing heavy loaded until they asked for it.
Trimming The Payload
The biggest lever on load time is the size of what you ship, and for most 2D games the heaviest things in the pck are textures. When I first exported World 11 I had art that was far larger on disk than it ever appeared on screen, source images sized for comfort during development rather than for delivery. Downscaling the oversized textures to the resolution they actually render at cut the payload meaningfully without any visible quality loss.
I went through the project, found the textures that were dramatically larger than their on screen footprint, and resized them down. Audio is the other usual suspect, since uncompressed clips add up quickly, so reasonable compression there helps too. The point is to look at your asset list sorted by size and ask whether each big file truly needs to be that big. Most of the time the answer is no.
The Branded Loader, The Part Everyone Misses
Now the detail that prompted me to write this whole post. By default, when a Godot web build is loading, the screen shows the Godot logo. It looks like the engine's splash, not your game's, and it quietly tells every player that this is a generic export. The reason is simple: the boot splash image is unset, so Godot falls back to its own logo.
The fix lives in Project Settings. Open Project, then Project Settings, enable Advanced Settings so the full tree shows, and find Application, then Boot Splash. There is an boot_splash/image field. Point it at your own logo file inside the project. While you are there, turn boot_splash/fullsize off so the image is centered at its natural size rather than stretched to fill the canvas, which keeps a clean logo looking clean. You can also set the background color so the loader matches your brand instead of sitting on plain black.
Then re export the Web build. That is the whole trick. The next time the game loads in the browser, the loading screen shows your art instead of Godot's, and suddenly the experience feels like it belongs to your game from the very first frame. It is a five minute change and it is the single highest leverage polish step on the entire web build, because it is the first thing every single player sees.
Putting It All Together
The full loop, start to finish, looks like this. Set up a Web export preset and install the templates. Turn thread_support off so you skip the cross-origin isolation headers and can use a plain static host. Export into a clean folder. Upload the files to a public object storage bucket behind a CDN, and set the wasm content type to application/wasm so streaming compilation works. Trim oversized textures before you export so the download stays light. Embed the game on your site behind a lazy iframe that only mounts on a Play click. And set boot_splash/image to your own logo with fullsize off so the loading screen is branded. None of these steps is hard on its own. The trouble is only that nobody lays them out in order, so you discover each one by losing an evening to it.
Play It
World 11 is live and free in your browser right now, built exactly this way. Spin a nation and a year, draft your eleven, and see how far your squad gets through the gauntlet. You can play it at games.kevingabeci.com. If you are shipping your own Godot game to the web, I hope this saved you the evenings it cost me.
Like this? You'll like what I'm building too.
Two ways to support and get more of this work.
HEARTH
A privacy-first Life OS for your desktop. Journal, tasks, and notes that stay on your machine. Coming soon, direct download from this site.
Read moreMY TOOLKITS
Receipts-first toolkits for shipping after hours, building Claude agents, publishing on Amazon, and more. The exact methods I used, not theory.
Browse on WhopRelated Articles
Balancing a Roguelike to a One-in-Three Win Rate With a Headless Harness
My draft roguelike was too easy. A strong team won the cup two-thirds of the time, so winning meant nothing. Here is how I stopped guessing and tuned it to a real one-in-three with a few hundred simulated runs.
Daily Challenges and Shareable Seeds, No Server Required
A deterministic, seeded simulation gave my draft game a daily challenge and shareable challenge links for free, with no backend at all. Here is how a single integer unlocks both.
Why I Made a Football Game Where You Never Touch the Ball
World 11 is a football game with no passing, shooting, or defending. You draft a team and watch the result. Here is why a game about betting instead of playing is so addictive, and how the draft loop works.