To export a Godot 4 game to the web, install the export templates, add a Web preset under Project > Export, leave Thread Support off, and export to a file named index.html. Serve the folder from a web server or zip it for itch.io. Opening the HTML file by double-clicking won't work.
We make Oryn, which produces a web build of every game automatically, so weigh that. The rest of this guide applies to any Godot 4 project. It's based on the Godot 4.7 documentation as it read on November 3, 2026; we didn't run new export tests for it. Where a number comes from one developer's project, we say so.
Check these three things before you export
The language. Godot's docs say C# projects in Godot 4 can't currently export to the web, and suggest Godot 3 if you need C# in the browser. If you're still choosing, Godot vs Unity covers how this affects the decision, since Unity's web export does take C#.
The renderer. Godot 4 on the web can only target WebGL 2.0 through the Compatibility rendering method. The docs say Godot doesn't support WebGPU yet, which Forward+ and Mobile would require. A Forward+ project still exports, because the project setting rendering/renderer/rendering_method.web defaults to gl_compatibility. It just won't look the same, as covered below.
Native plugins. GDExtensions need to be compiled for the web specifically, and loading them needs Extensions Support and the same cross-origin headers as threads. A plugin written in pure GDScript has no such problem, so check your addons folder first.
How to export a Godot game to the web
Install the export templates
Open Editor > Manage Export Templates, tick the platform you need and click Install Selected Templates. You can also install from a TPZ file downloaded from the Godot website (exporting projects docs). If your game uses emoji or languages such as Chinese or Japanese, install the ICU Data option too. Templates must match your editor version.
Add a Web preset
Open Project > Export, click Add… and choose Web. The defaults suit most games. If templates are missing, the dialog shows an error and blocks the export until you fix it.
Set the options that matter
Leave Thread Support off unless you've read the next section. Tick VRAM Texture Compression > For Mobile if phones are a target; for a game that also targets desktop browsers, keep For Desktop on as well. Enabling both gives a bigger but more compatible export.
Export to an empty folder as index.html
Click Export Project and name the file
index.html, since web servers load that by default. You get several files side by side:.html,.js(startup code),.wasm(the engine),.pck(your game) and.png(the boot splash). Keep the names you exported with, because the docs warn that renaming them can cause problems.Test it on a local server
Save Godot's serve.py into the export folder and run
python serve.py --root .. It sends the two cross-origin headers, so threaded builds work too. It's for testing only; the docs say to use a server such as Apache or nginx in production. If a runnable web template is installed, the editor also shows a button near the play controls that opens the game in your default browser.Open the browser console
Press F12, or Ctrl + Shift + I (Cmd + Option + I on macOS), to see JavaScript, engine and WebGL errors. Godot may capture those shortcuts while the game has focus. If so, open the developer tools from the browser's menu.
To export from a build script, name the preset and output path. The output directory has to exist already (command line docs).
godot --headless --path path/to/project --export-release "Web" build/web/index.html
Threads or no threads
Threaded web builds use SharedArrayBuffer, and browsers restricted that API after the Spectre and Meltdown exploits. Godot's 4.3 web export report says the team "bet on the wrong horse" by going all-in on threads, and then brought single-threaded exports back. That's now the default, and the docs call it the preferred way to export.
| Single-threaded (default) | Thread Support on | |
|---|---|---|
| Headers needed | None | COOP and COEP, over HTTPS or localhost |
| Ads and third-party integrations on the page | Allowed | Ruled out by cross-origin isolation |
| itch.io, Poki, CrazyGames | Works better, per the docs | Needs extra setup, or may not work |
| macOS and iOS browsers | Works well, per the docs | Long history of compatibility issues |
| Performance | Lower; no threads | Higher |
| Audio latency | Low with default Sample playback | Low with Stream playback too |
Go threaded if profiling shows your game needs the performance and you control the server. Otherwise stay on the default.
The headers for a threaded build
The server has to send exactly these two response headers, from a secure context (HTTPS, with localhost usually exempt):
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Without them, or the workaround below, the docs say the project won't run. Here's a sample nginx block with compression:
location /game/ {
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
gzip on;
gzip_types application/wasm application/octet-stream application/javascript;
}
Hosts that read a _headers file take this form, at the root of the published folder (Netlify, Cloudflare Pages):
/*
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
If you can't set headers at all, tick Progressive Web App > Enable in the export options. Its service worker simulates them, with Enable Cross Origin Isolation Headers on, and still needs HTTPS. It also makes the game installable and able to load offline after the first visit, and adds the stale-build problem listed under errors.
Compatibility renderer: what changes on the web
If you built in Forward+, expect your game to look different in the browser. Export early, because lighting and environment may need manual changes when you move to or from Compatibility, and that gets more expensive the later you find out.
These are marked unsupported in Compatibility on the renderers page, as of Godot 4.7:
- SDFGI and VoxelGI global illumination
- Screen-space reflections and SSIL
- Volumetric fog
- Depth of field blur
- TAA and FSR2
- Particle trails and particle SDF collision
SSAO and glow are supported. OmniLights and SpotLights are limited to 8 per mesh by default (Forward+ allows 512 per cluster), and you can raise that. To preview all of this without exporting, click the renderer name at the top right of the editor and switch your project to Compatibility, or start new web-first projects there.
Safari is the browser to test separately. The docs say it has WebGL 2.0 issues that other browsers don't, and they recommend Chromium-based browsers or Firefox.
File size and load time
A web player downloads everything before the game starts, so size affects whether they stay. Two sets of numbers from real exports give a rough baseline, and neither is an official figure:
- One Godot 4.3 user reported a 35 MB
.wasmand a 56 MB.pck. - One developer's test of a tiny 2D project measured a default web export of 42.0 MB, or 8.72 MB zipped, on Godot 4.4.
In order of effort, here's what helps.
Turn on compression at the host. This is the largest single gain. The docs say the .wasm goes down to around a quarter of its size with gzip, and that Brotli precompression can save more. GitHub Pages gzips on the fly. The docs list itch.io and GitLab Pages as hosts that don't, though GitLab accepts files you gzip yourself. Serve .wasm as application/wasm, because another MIME type can prevent startup optimizations, and .pck as application/octet-stream.
Shrink the .pck. That's your game's content, so it's the file you have direct control over. Exclude editor-only and source files with the Resources tab's filters in the export preset, and look at your largest textures and audio tracks first. Only enable the VRAM compression formats your targets need, since each format adds data.
Compile a custom template. This is the big step for the engine itself, and the docs recommend it for mobile load times. On Optimizing a build for size, the main switches are disable_3d=yes (about 15% smaller, for 2D games only), disable_advanced_gui=yes and a build_profile generated by the editor's engine compilation configuration tool. The profile can be too aggressive about features only created at runtime, so test afterward. The web already compiles for size by default, so optimize=size won't save much there.
That same test went from 42.0 MB to 17.4 MB after stacking these options, ending with a compilation profile. Zipped, that was 3.70 MB, and Brotli brought it to 2.70 MB. Its project was close to a best case, with a bouncing sprite and one label, so expect less from a real game. You'll need Emscripten and a working build setup; Compiling for the Web has the steps, and you point the preset's custom template field at the result.
Audio and the user-gesture rule
Browsers restrict sound that starts by itself. The docs' fix is to ask the player to click, tap or press a key first, typically on a splash or title screen. So make your first scene a start button, and begin the music after it:
func _on_start_button_pressed():
MusicPlayer.play()
get_tree().change_scene_to_file("res://game.tscn")
MusicPlayer here is an autoload, so the track keeps playing through the scene change.
The second audio surprise is the playback mode. Since 4.3, web exports play sound as Web Audio samples by default, which keeps latency low without threads and prevents garbled audio in single-threaded builds. In that mode:
- AudioEffects don't work, including effects on audio buses.
- Reverb and doppler don't work.
- Procedural audio generation doesn't work.
- Positional audio may misbehave, depending on the node's properties.
If your game relies on these, set Audio > General > Default Playback Type.web to Stream, or set Playback Type to Stream on a single AudioStreamPlayer, AudioStreamPlayer2D or AudioStreamPlayer3D. You get the full feature set back at the cost of more latency, especially without threads. Setting only the node that needs an effect keeps the rest of the game on samples.
For the music itself, see our guides to royalty-free game music and AI game music and sound.
Mobile browsers
Web builds run on phones with caveats. The docs say native Android and iOS exports perform far better, and that WebAssembly adds overhead on hardware that's already constrained. The upside is that players skip the app store. If you can ship native as well, how to make a mobile game covers that route.
To make the web version hold up on phones:
-
Lower settings for mobile browsers. Godot has feature tags for the OS behind the browser, checked at runtime:
var mobile_web := OS.has_feature("web_android") or OS.has_feature("web_ios") -
Compress for mobile GPUs. Tick For Mobile in the preset, and if the export dialog asks for it, enable Import ETC2 ASTC under Project Settings > Rendering > Textures > VRAM Compression. It's off by default.
-
Handle touch.
emulate_mouse_from_touchis on by default, so buttons respond to taps, but anything using key presses needs on-screen controls. For text input on phones, there's an experimental option in the preset,html/experimental_virtual_keyboard. -
Set a stretch mode. The canvas matches the browser window by default, so phone screens and rotation will change your layout. Godot's multiple resolutions guide covers the settings, and Canvas Resize Policy gives you fixed-size control when you want it.
-
Watch memory on Safari. Godot's 4.3 report noted that even its Platformer 2D demo could trigger Safari's "this webpage is using significant memory" reload. Test on a real iPhone.
To lock orientation for players who add the game to their home screen, use the PWA options, which include an orientation setting.
Where to host it
| Host | Compression | Custom headers | Limits to know |
|---|---|---|---|
| itch.io | None on the fly, per Godot's docs | Experimental opt-in setting | Reported at 1,000 files, 500 MB total, 200 MB per file |
| GitHub Pages | Gzip | Use the PWA workaround | 1 GB site, 100 GB a month soft limit |
| Netlify | Not checked | _headers file | Not checked |
| Cloudflare Pages | Not checked | _headers file | 25 MiB per file, 20,000 files on the free plan |
| Your own server | You configure it | You configure them | Yours |
itch.io
Zip the export folder's contents so index.html sits at the top level, since itch's HTML5 guide says the zip must contain a file called index.html. Upload it, set the project's kind to HTML, mark the upload as playable in the browser, and set the embed size to your game's resolution.
Single-threaded builds need nothing more. For a threaded build, the project's embed options have a SharedArrayBuffer support checkbox (itch.io announcement). Treat it as a partial fix: itch labels it experimental, and Godot's report says it relies on a feature that Safari and Firefox for Android don't support.
We couldn't load itch's guide when writing this, because the page blocked our fetch. The limits in the table come from a guide to web game platforms and a forum thread, so check them against the official page before a big upload. That same third-party guide says hosted paths are case-sensitive, which matches the Godot warning below.
GitHub Pages, Netlify and Cloudflare Pages
For a single-threaded build, any static host works: upload the folder and share the link. GitHub Pages is the easiest free route, with a 1 GB limit on published sites.
Cloudflare Pages has a catch for Godot specifically. Its maximum file size is 25 MiB, so a stock .wasm reported at 35 MB wouldn't fit, and neither would a larger .pck. Cloudflare's suggestion for larger files is to store them in R2, and a custom template can also bring the engine under the limit.
Web game portals
The docs say single-threaded export is more compatible with publishers such as Poki and CrazyGames. Each portal has its own submission rules and SDK requirements, which we didn't review. Because threaded builds rule out third-party integrations on the page, plan on single-threaded if a portal or ad network is your target.
Common Godot web export errors and fixes
Missing export templates or a blocked Export button. Install the templates for your exact editor version under Editor > Manage Export Templates. Custom engine builds need custom templates.
"The following features required to run Godot projects on the Web are missing: Cross Origin Isolation, SharedArrayBuffer." You'll find this one all over the forums (example thread). The build expects threads and the host isn't sending the headers. Turn off Thread Support and export again, which is the right fix for most games. If you're on Godot 4.0 to 4.2, upgrade to 4.3 or newer to get single-threaded export. Otherwise add the headers, use the PWA option or tick itch's checkbox.
Blank page or CORS errors when opening index.html from disk. Browsers treat local files as opaque origins, so loading the game's other files fails on file:/// URLs. Run a local server.
Missing files that worked in the editor. Godot's PCK virtual filesystem is case-sensitive, while Windows and macOS usually aren't, so res://Player.png and res://player.png behave differently after export. The docs recommend snake_case for all files. Files that Godot doesn't import, such as a JSON data file, also need adding to the preset's non-resource filter.
Dark or different-looking 3D. That's the Compatibility renderer. Rebalance lighting and environment with the project switched to Compatibility, and replace the unsupported effects listed above.
The export is blocked because the project uses C#. The docs offer no Godot 4 route. Port the scripts to GDScript or use Godot 3.
No sound. The browser is waiting for a user gesture, so add a start screen. If sound plays but effects don't, you're on Sample playback; see the audio section.
An old version of the game keeps loading. With PWA or one-click deploy, a service worker can serve a previous build, and sometimes even a different project. In developer tools, go to the Application tab, then unregister the service worker and reload (docs).
Saves disappear. user:// lives in IndexedDB. Players must allow cookies, games in an iframe, as on many portals, need third-party cookies too, and private browsing doesn't persist. OS.is_userfs_persistent() can check this, though the docs warn it may give false positives. Tell players when a save might not stick.
Fullscreen or mouse capture does nothing. Browsers only allow these in response to an input event, so do it inside the callback. Checking the Input singleton in _process isn't enough:
func _unhandled_input(event):
if event.is_action_pressed("toggle_fullscreen"):
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
The fullscreen project setting has the same problem at startup, and only a custom HTML shell can work around it.
Gamepad isn't detected. Gamepads don't appear until a button is pressed, they need a secure context, and mappings can be wrong because the browser can't reliably identify the model. Show a "press any button" prompt.
The game stops when I switch tabs. The browser pauses the project in an inactive tab, so _process() and _physics_process() stop, and networked games may disconnect. A separate, unfocused window keeps running.
Multiplayer or HTTP calls fail. Low-level networking isn't available. You have the HTTP classes, WebSocket (client) and WebRTC, and HTTP is subject to the same-origin policy, with no chunked responses or blocking mode. Calls to your own API from another domain need CORS on that API.
My edits to the HTML are gone. Godot regenerates the HTML page on each export. Use Custom HTML shell or Head Include in the preset, as described in Custom HTML page for Web export.
Once it runs on your host, test the final link in Chrome, Firefox and Safari on a computer, then on one Android phone and one iPhone. To see what the engine has shipped, look through games made with Godot.
Frequently asked questions
Can Godot 4 export to HTML5?
Yes. Godot's Web export produces an HTML page, a WebAssembly engine and a .pck of your game, and it needs WebAssembly and WebGL 2.0 in the player's browser. GDScript projects export normally. C# projects can't export to the web in Godot 4 as of the 4.7 docs.
Why doesn't my Godot web game work on itch.io?
The most common cause is a threaded build, which shows an error about Cross Origin Isolation and SharedArrayBuffer. Turn off Thread Support and export again, using Godot 4.3 or newer. Also check that index.html sits at the top level of your zip.
Do I need SharedArrayBuffer for a Godot web export?
Only if you enable Thread Support or Extensions Support. Those need the Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers. The default single-threaded export since Godot 4.3 needs neither.
Does Godot web export support Forward+ or WebGPU?
Stock Godot doesn't. The 4.7 docs say Godot can only target WebGL 2.0 through the Compatibility renderer and doesn't support WebGPU yet, so a Forward+ project exports using Compatibility. Features such as SDFGI, volumetric fog and screen-space reflections aren't available there.
How do I make a Godot web export smaller?
Serve it with gzip or Brotli first, since gzip takes the .wasm to about a quarter of its size. Then cut large textures and audio from the .pck, and if you need more, compile a custom web template with unused engine features disabled. One developer's small 2D test went from 42.0 MB to 17.4 MB that way.
Do Godot web games work on phones?
Yes, with lower performance than native exports. Enable VRAM compression for mobile, add touch controls and a tap-to-start screen, and test on real devices, especially Safari on iPhone. Use the web_android and web_ios feature tags to lower settings at runtime.