Custom Godot WebGL Template: Loading Screen, JS Bridge, and the HTML Shell

Custom Godot WebGL Template: Loading Screen, JS Bridge, and the HTML Shell

Honesty note: this is Godot’s documented template system as of 4.x, mapped for a browser-game use case. I ran my Godot H5 test with the default template and hit its two limits (plain loading UI, no easy JS bridge) — the fixes below are the standard documented ones (my Godot context).

TL;DR

  1. Every Godot web export is an HTML shell + engine files: index.html (the template), index.wasm, index.pck, index.js. The template is just an HTML page that loads the engine — and you can replace it (file set).
  2. Customizing the template means copying Godot’s default HTML and editing it: export → HTML5 → “HTML Shell” setting points to your .html file. Common edits: a branded loading screen, progress bar, and post-load callbacks.
  3. JavaScriptBridge is the JS↔GDScript bridge: JavaScriptBridge.eval() / JavaScriptBridge.get_interface() call JS from GDScript, and you can expose GDScript callbacks to the page. This is how a template can drive game UI, or the page can drive the game (e.g. portal SDK calls).
  4. The template is also your embed contract: when a portal iframes your game, the template’s startGame / resize logic decides how it behaves in the iframe (portal reality). Keep the default template structure intact and extend it.

How the template system works

Export → HTML5 → HTML Shell: a file path (default is Godot’s built-in godot.template.html). The engine looks for specific elements/IDs in the shell to attach to; the documented contract:

The safe path: export once with the default shell to get the generated godot.template.html, copy it, edit the copy (loading text, styles, callbacks), and point the export at your copy. Don’t hand-write a shell from scratch — the contract details are easy to get wrong.

What to customize (in priority order)

  1. Loading screen — replace the default “Loading…” with a branded progress UI; the engine exposes progress callbacks the shell can listen to.
  2. Pre/post-load hooks — show a “Tap to start” overlay (mobile browsers need a user gesture for audio), fire startGame() from a button.
  3. Resize behavior — make the shell adapt the canvas to its container/iframe (portals size you; your own page can letterbox).
  4. JS bridge — call into the page or get called by it.

The JavaScriptBridge pattern (30 seconds)

# In GDScript: call a JS function on the page
JavaScriptBridge.eval("window.myGameBridge('level_complete')")

# Expose GDScript to the page: register a callback the page can call
# (via the template's JS calling the engine's exposed object)

Typical uses: send analytics events to the page’s tracker, trigger a portal SDK call, receive a “pause/resume” signal from the embedding page. On the web export, the bridge is the only sanctioned path between your GDScript and the host page (my JS/SDK reality).

Template + portal iframes (the honest part)

When a portal (CrazyGames/Poki) iframes your export, your template runs inside their page context:

Pitfalls

  1. Hand-writing the shell — copy Godot’s generated template and edit; the contract is subtle.
  2. Over-branded loading UI — portals mask the template anyway; put the effort where your own hosting shows it.
  3. JS bridge without checking JavaScriptBridge availability — the class only exists on web exports; guard for other platforms (touch note).
  4. Forgetting resize/iframe behavior — a template that assumes full-window breaks inside an iframe.
  5. Mixing custom JS with the threaded build — the SAB/COOP/COEP constraints still apply to your shell’s third-party scripts (export warnings).

Bottom line

The Godot web template is just an HTML shell you can customize: copy the generated default, edit the loading screen and hooks, use JavaScriptBridge for page↔game calls, and keep iframe behavior in mind for portal embeds. The template is the web layer of your Godot game — and for the size/performance reality around it, the rest of my Godot posts cover the edges.