GuidesUNITY WEBGL
Fix UnityWebRequest CORS errors in Unity WebGL builds
A Unity WebGL build runs inside the browser, so UnityWebRequest goes through the browser Fetch API and every call to another domain needs CORS headers from that server; without them the browser logs Cross-Origin Request Blocked and the game only sees a connection error with a generic message. The same request works in the Editor and in desktop builds. For public GET requests, prefix the URL with https://proxy.cors.dev/ in WebGL builds: free, no signup, no API key, JSON and XML responses up to 1 MiB within 10 seconds. For a server you run, add the headers there.
The error: generic in Unity, specific in the browser
Unity cannot see why the browser refused the response; request.result is ConnectionError, responseCode is 0 and request.error is a generic message. Press F12 in the browser running the build and read the console: it names the blocked URL and the reason. Status code: 200 in the Firefox message means the server answered fine and only the header was missing, which is the common case for public APIs.
# Unity (request.result == ConnectionError, request.error is generic)
Request failed: 0 Unknown Error
# Browser DevTools console, Firefox
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource
at https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=440&count=3.
(Reason: CORS header 'Access-Control-Allow-Origin' missing). Status code: 200.
# Browser DevTools console, Chrome
Access to fetch at 'https://api.steampowered.com/...' from origin 'https://yourgame.itch.io'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.Why WebGL is different
In the Editor and in Windows, macOS, Android or iOS builds, UnityWebRequest opens its own HTTP connections and no browser policy applies. A WebGL build is JavaScript and WebAssembly inside a page, so its requests are page requests: the browser sends them, then withholds any cross-origin response that lacks Access-Control-Allow-Origin for the page origin. Unity documents this for the Web platform, together with the other sandbox limits: no raw TCP or UDP sockets, no System.Net classes, no blocking waits on a download.
The page origin is wherever the build is hosted: your own domain, GitHub Pages, or the frame a game host such as itch.io embeds it in. A server that allows one of those origins by name breaks when you move the build, which is why public game APIs either send * or send nothing at all.
Public APIs: prefix a proxy in WebGL builds only
When the API is not yours, no setting in Unity fixes it; the header has to come from a server. A CORS proxy fetches the API from its own servers and relays the response with Access-Control-Allow-Origin set to the page origin. Use a compile-time check so only WebGL builds pay the extra hop. The example reads Steam news for an app id, a public JSON endpoint that needs no key and sends no CORS header.
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class SteamNews : MonoBehaviour
{
const string Proxy = "https://proxy.cors.dev/";
const string Target = "https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=440&count=3";
IEnumerator Start()
{
#if UNITY_WEBGL && !UNITY_EDITOR
// Only browsers enforce CORS: the Editor and native builds call the API directly
var url = Proxy + Target;
#else
var url = Target;
#endif
using var request = UnityWebRequest.Get(url);
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
// X-Cors-Error is set when the proxy refused or could not complete the request
var code = request.GetResponseHeader("X-Cors-Error") ?? request.error;
Debug.LogError($"Request failed: {request.responseCode} {code}");
yield break;
}
var json = request.downloadHandler.text;
Debug.Log(json);
}
}Anonymous requests are GET and HEAD to any public HTTPS host, with Accept, If-None-Match and If-Modified-Since as the only forwarded request headers; a custom header set with SetRequestHeader triggers a preflight and is rejected with header_not_allowed. Responses must be text (JSON, XML, plain text) up to 1 MiB. Steam Web API methods that require ?key= are rejected anonymously because the query name looks like a credential; a Pro Connection allows them, but a key compiled into a WebGL build is public, so keep keyed calls on your own server.
Your own server: send the headers
For a leaderboard, save or matchmaking API you run, add the headers on the server and skip the proxy entirely. A POST with Content-Type: application/json or any custom header triggers an OPTIONS preflight first, so the server must answer that with 204 and the same headers. Framework guides: Express, FastAPI, Spring Boot, Cloudflare Workers.
# Answer the preflight (OPTIONS) and the real request with these headers
Access-Control-Allow-Origin: https://yourgame.example
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Access-Token
Access-Control-Max-Age: 600
# Use the exact origin the game runs on (itch.io, GitHub Pages, your domain).
# "*" works only for requests without cookies or Authorization.Limits and pricing
| Limit | Free, no key | Pro, $5 per month |
|---|---|---|
| Price | $0 | $5 per month; 7-day trial with 1,000 requests, no card |
| Methods | GET and HEAD | GET, HEAD, POST, PUT, PATCH and DELETE |
| Destinations | Any public HTTPS host | Public HTTPS hosts enabled on your Connection |
| Response size | 1 MiB, text types only | 6 MiB, any content type |
| Request body | None | 1 MiB |
| Upstream deadline | 10 seconds | 10 seconds |
| Rate | Shared pool with fair-use limits | 600 requests per minute, 10 concurrent per account |
| Monthly requests | No quota | 500,000 per billing period, $5 per extra 500,000 |
| Request headers forwarded | Accept, If-None-Match, If-Modified-Since | Plus Authorization and custom headers |
| Credential-like query names (key, token) | Rejected | Allowed |
| Caching | None, no-store | Opt-in X-Cors-Cache, 1 to 300 seconds |
Good questions.
Why does the request work in the Editor and fail in the browser?
The Editor is not a browser. Only browsers apply the same-origin policy and demand Access-Control-Allow-Origin on cross-origin responses, so a WebGL build is the first place the missing header matters.
Why is request.error just Unknown Error?
The browser hides the details of a blocked cross-origin response from page code on purpose, and the Unity runtime only receives the failure. The specific reason is printed in the browser console.
Can I call the Steam Web API with my key from a WebGL build?
Technically on a Pro Connection, but your key ships inside the build where anyone can copy it. Call keyed Steam methods from your own server and expose only what the game needs; keyless methods such as ISteamNews work through the free proxy.
Does the proxy support POST for leaderboards or saves?
POST, PUT, PATCH and DELETE with bodies up to 1 MiB need a Pro Connection with the host enabled, for $5 per month. Since those endpoints are usually yours, adding the headers on your server is the better fix.
Does this cover WebSockets or asset downloads?
No. The proxy relays HTTP requests only, and the free tier relays text responses only. AssetBundles, images and audio are binary: host them on a server or CDN that sends Access-Control-Allow-Origin: *, or use Pro, which relays any content type up to 6 MiB.
Will a browser extension that disables CORS fix it for players?
Only on the machine where it is installed. Players run normal browsers, so the build must work with real headers or a proxy. Extensions and flags are development aids at most.