Browser Games Need a Different Backend: WebGL Limits, Guest Logins and Saves That Survive a Cache Clear

Unity WebGL and Godot web exports cannot write files, cannot open raw sockets, and cannot load native SDK plugins - which is why so many devs end up writing their own wrapper just to talk to a backend. What actually breaks on web builds, why local saves vanish, why guest login is mandatory on itch.io, and how to keep the whole thing to a few HTTP calls.

Quick take

  • Web builds cannot write save files, cannot open TCP/UDP sockets, and cannot load native SDK plugins. Most backend SDKs assume all three, which is why people end up writing their own wrapper.
  • Browser-local storage is not a save system. It lives in IndexedDB, it is per-browser, and it disappears when the player clears site data or opens your game on their phone.
  • On a portal like itch.io, almost nobody will create an account to play a free web game. Guest login first, upgrade to a real account later, or you lose the player at the form.
  • Every request from a web build is subject to CORS. A backend that does not send the right headers simply cannot be called from your game, no matter how correct your code is.
  • Download size is a feature on web. A multi-megabyte SDK is paid for by every single visitor before they see your title screen.

There is a post that shows up periodically and always rings true: "instead of making my game, I ended up writing a Firebase wrapper for Unity WebGL... it was worth it." Nobody sets out to write a backend wrapper. It happens because the standard SDK does not work in a web build, and the reasons are structural rather than anybody's bug.

What actually breaks in a web build

Both Unity WebGL and Godot's web export run inside the browser sandbox, and the sandbox removes four things backend code normally assumes:

  • No filesystem. There is no File.WriteAllText and no writable user:// directory in the sense you are used to. Persistence is routed into browser storage (IndexedDB) behind the engine's own API. Any library that writes a cache or token file to disk fails here.
  • No raw sockets. A browser cannot open a TCP or UDP connection. Your only transports are HTTP requests and WebSockets. Every backend SDK built on a custom UDP protocol is unusable in a web build, and so is any "connect to the game server directly" flow.
  • No native plugins. Native .dll/.so/.a binaries do not exist in WebAssembly. Many vendor SDKs ship exactly that for their networking or crypto layer, so they compile everywhere except web. This is the single most common reason a documented SDK "just does not work".
  • Limited threading. Web builds are constrained around threads (Godot needs cross-origin isolation headers to enable them at all), so SDKs that spin background worker threads for their transport tend to misbehave.

Add CORS on top: every request your game makes is a cross-origin request from the page hosting your build, and the browser will refuse it unless the API answers with permissive headers. You can write perfect code against a perfectly good API and still get nothing, because the preflight failed.

Why local saves quietly lose player progress

The default "save" in a web build is browser storage, and it has three properties that make it unfit as your only save:

  • It is per-browser and per-origin. Chrome on the desktop and Safari on the phone are two different players as far as your game is concerned. So is the same browser in private mode.
  • The player can wipe it without meaning to. "Clear browsing data" and aggressive privacy modes remove it. There is no warning that a game save was in there.
  • The portal can change the origin. If your build moves, or the portal serves it from a different subdomain, storage keyed to the old origin is gone.

For a jam entry, fine. For anything with progression, the save has to live server-side, keyed to an identity that follows the player. Which raises the identity problem, and on web that problem has a specific shape.

Guest login is not optional on web

On a portal, your player clicked a link out of curiosity. They are one tab away from leaving. If your first screen asks for an email and a password, a large share of them will simply not do it - and they were never going to, no matter how good the game is.

So the flow that works on web is the reverse of the flow that works on Steam:

  1. Play immediately. On first launch, create an anonymous player behind the scenes and store its token in browser storage. No screen, no friction. Progress starts saving from minute one.
  2. Offer the upgrade later, with a reason. Once they have something to lose - a score on the board, ten levels of progress - offer to attach an email or an OAuth provider so their save follows them to another device. Now the account has a visible benefit, and conversion is much better than at the door.
  3. Keep the same player identity through the upgrade. The upgrade must attach credentials to the existing anonymous player, not create a new one and orphan the progress. If it creates a second player, you have built a progress-loss bug with a signup form on top. Guest login and account upgrade covers the mechanics.

Note the interaction with browser storage: the anonymous token lives in the same storage that gets cleared. That is not a reason to skip guest login - it is the strongest reason to offer the upgrade before the player has too much invested.

Download size is part of your backend decision

On a store page, a 30 MB SDK is invisible. On the web it is the wait between a click and your title screen, and web audiences bounce during that wait. This changes what "a good SDK" means:

  • A backend you call with plain HTTP and JSON costs you almost nothing in build size, because the engine already ships an HTTP client.
  • A vendor SDK that pulls in a transport layer, a crypto layer and a serialiser costs real megabytes, and on web you pay it per visitor rather than once per install.

This is the practical argument for a REST-shaped backend on web builds, independent of any vendor: the smallest possible client is the one that is already in the engine.

A working shape for a web game

  1. Anonymous sign-in on first launch, token cached in browser storage.
  2. Progress in a server-side player document - written on meaningful events, not every frame. Version it, because your save format will change every patch and a web player might return after six months on a build you have replaced twice.
  3. Leaderboards server-validated, because a browser game is the easiest possible target for a forged score: the network tab is right there. The trust problem is worse on web, not better.
  4. Account upgrade prompted after the first real achievement, attaching to the same player.
  5. No raw sockets anywhere in the design. If the game needs real-time, plan for WebSockets from the start rather than discovering the limit late.

FAQ

Why does the Firebase / vendor SDK not work in my Unity WebGL build?
Usually because it ships native code for its transport or crypto layer, and native binaries do not exist in WebAssembly. The workaround people land on is calling the REST API directly, or writing a small JavaScript bridge - which is exactly the "I wrote my own wrapper" outcome. A backend that is REST-first needs no wrapper.

Can I keep using PlayerPrefs / local storage for saves?
As a cache, yes. As the only copy, no. It is per-browser, per-origin, and the player can delete it without realising a save was in there. Anything you would be upset to lose belongs server-side.

Do I need HTTPS?
Yes. Portals serve your build over HTTPS, and a secure page cannot make plain-HTTP requests. Any backend you call must be HTTPS, and it must return CORS headers that permit the portal's origin.

Is real-time multiplayer possible in a browser game?
Yes, over WebSockets or WebRTC, not over raw UDP. Expect higher latency and a different reliability model than a native build, and design the gameplay for it rather than porting a UDP design and hoping.

Where Crux fits

Crux is REST-first, which is the property that matters for web builds: there is no native plugin to fail, nothing to wrap, and no SDK weight to download - a web game talks to it with the HTTP client the engine already has. Anonymous sign-in gets a player saving from the first minute, the upgrade to email or OAuth attaches to that same player so progress follows them across devices, saves are versioned documents you can migrate as your game changes, and leaderboard scores are validated server-side rather than taken on trust from a browser. If you are currently evaluating a general-purpose backend for a game, the Firebase-for-games comparison covers where that path gets awkward, and the 15-minute leaderboard walkthrough is the fastest way to see the shape of the API.

The one-line version: the browser takes away files, sockets and native code. Pick a backend that never needed any of them.