GuidesGODOT WEB
Godot web export CORS errors
In a Godot 4 Web export, HTTPRequest runs on the browser's fetch, so a request that works in the editor fails once the game runs in a browser and the API sends no Access-Control-Allow-Origin. On itch.io the request comes from https://html-classic.itch.zone, not from your itch.io page. Prefix the URL with https://proxy.cors.dev/ in Web builds only: GET is free with no signup (about 600 requests a minute per IP), and POST for leaderboards needs cors.dev Pro at $5 a month for 500,000 requests.
Why HTTPRequest fails only in Web exports
In the editor and in desktop or mobile builds, Godot uses its own networking, and CORS does not apply. In a Web export, Godot's HTTP classes run on the browser's fetch API (platform/web/js/libs/library_godot_fetch.js in the engine source), so the browser enforces CORS on every request. That is why the same HTTPRequest call works in the editor and fails in the browser.
Godot's HTTPClient class reference (Godot 4.7 stable docs), which the HTTPRequest page points to: "When performing HTTP requests from a project exported to Web, keep in mind the remote server may not allow requests from foreign origins due to CORS. If you host the server in question, you should modify its backend to allow requests from foreign origins by adding the Access-Control-Allow-Origin: * HTTP header." When you do not run the server, you cannot add that header.
The origin your game sends
The editor's Run in Browser button serves the export from http://localhost:8060 by default (editor settings export/web/http_host and export/web/http_port), and requests carry that origin.
itch.io runs HTML5 games inside an iframe served from https://html-classic.itch.zone. On https://itch.io/embed-upload/3263210, checked on 28 October 2026, the page origin was https://itch.io and the game frame loaded from https://html-classic.itch.zone/html/3263210/.... Requests from the game carry Origin: https://html-classic.itch.zone, not your itch.io page. Games that turn on itch.io's SharedArrayBuffer support are served from https://html.itch.zone instead.
| Where it runs | Origin an API sees | CORS applies | proxy.cors.dev free answers |
|---|---|---|---|
| Editor, desktop and mobile builds | None, native networking | No | Not needed |
| Run in Browser | http://localhost:8060 | Yes | Echoes http://localhost:8060 |
| itch.io | https://html-classic.itch.zone | Yes | Echoes the origin |
| itch.io with SharedArrayBuffer | https://html.itch.zone | Yes | Echoes the origin |
| Your own website | Your domain | Yes | Echoes the origin |
The Steam news API (https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=440&count=3&maxlength=300&feeds=steam_community_announcements, 2,083 bytes of JSON) sends no Access-Control-Allow-Origin, so a browser blocks it. On 28 October 2026 it answered 200 through https://proxy.cors.dev/ with the header echoing https://html-classic.itch.zone, https://html.itch.zone and http://localhost:8060.
Add the proxy in Web builds only
Put https://proxy.cors.dev/ in front of the full target URL; the free tier needs no signup and no key. In GDScript, add the prefix only when OS.has_feature("web") is true (the web feature tag means the game runs in a browser), so the editor and native builds keep calling the API directly.
extends Node
const PROXY := "https://proxy.cors.dev/"
const NEWS_URL := "https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=440&count=3&maxlength=300&feeds=steam_community_announcements"
func _ready() -> void:
var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(_on_news_completed)
var url := PROXY + NEWS_URL if OS.has_feature("web") else NEWS_URL
var error := http.request(url)
if error != OK:
push_error("Could not start the request: %s" % error_string(error))
func _on_news_completed(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS or code != 200:
push_error("News request failed: result %d, HTTP %d" % [result, code])
return
var data = JSON.parse_string(body.get_string_from_utf8())
if data == null:
push_error("The response was not JSON")
return
for item in data["appnews"]["newsitems"]:
print(item["title"])The request_completed handler checks result == HTTPRequest.RESULT_SUCCESS and the response code, then parses the body with JSON.parse_string(body.get_string_from_utf8()). We ran this script in Godot 4.7.2 headless, direct and through the proxy, and it printed three Team Fortress 2 news titles both times.
One HTTPRequest node handles one request at a time: request() returns ERR_BUSY while the previous request is running, so create one node per parallel request.
The free tier's rules:
- Methods:
GETandHEADonly;POSTanswers405method_not_allowed. - Rate: About 600 requests per minute per IP and no monthly quota; past that,
429with aRetry-Afterheader. - Headers:
Authorization, and names containingtoken,apikey,api-key,api_key,secretorpasswordor ending in-key, answer400header_not_allowed. Other custom headers, including names containingcsrforxsrfsuch asX-CSRF-Token, are dropped and listed inX-Cors-Dropped-Headers. - Size: Responses up to 6 MiB within a 30 second upstream deadline, on both tiers.
When the proxy itself refuses a request, the X-Cors-Error response header names the reason; X-Cors-Source: upstream marks status codes that came from the API.
Leaderboards and other POST requests
A POST from Origin: https://html-classic.itch.zone got 405 method_not_allowed on the free tier. Score submissions and other POST, PUT, PATCH or DELETE calls need cors.dev Pro: $5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card. Pro forwards bodies up to 1 MiB and Authorization, Content-Type and the custom headers you configure, at 3,000 requests a minute per account and 100 in flight.
- Create a Connection
Sign up with Google; a Connection lists up to 10 exact page origins and up to 20 target hosts.
- Allow your origins and host
Register
https://html-classic.itch.zone(orhttps://html.itch.zone) andhttp://localhost:8060, and enable your API's host. Any other origin answers403origin_not_allowed, andOrigin: nullnever matches. - Send the key from GDScript
HTTPRequest.request()takes custom headers as aPackedStringArray, for example["X-Cors-Key: YOUR_CONNECTION_KEY", "Content-Type: application/json"], thenHTTPClient.METHOD_POSTand the body string.
Every itch.io game shares the html-classic.itch.zone origin, so an origin check cannot tell your game from another itch.io game; the Connection's list of target hosts is what limits the publishable key. Never put a secret API key in a Web export: players can read everything the game downloads.
Other Web export limits
Godot's "Exporting for the Web" page lists limits beyond CORS. Web builds support only the HTTP client, HTTP requests, the WebSocket client and WebRTC, and HTTP is subject to the same-origin policy. HTTP requests cannot progress more than once per frame, chunked responses are not supported, and host verification cannot be disabled.
Threaded Web exports need cross-origin isolation headers, Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. Since Godot 4.3 the default Web export is single-threaded, which Godot calls more compatible with stores like itch.io.
Good questions.
Why does my game work in the editor but not on itch.io?
The editor and native builds use Godot's own networking, where CORS does not apply. A Web export sends requests through the browser's fetch, from the https://html-classic.itch.zone origin on itch.io, so the browser blocks any response that lacks an Access-Control-Allow-Origin header for it.
Can a Godot project setting turn off CORS?
No. The browser enforces CORS, not the engine, and no export option changes it. Either the server adds Access-Control-Allow-Origin or the Web build sends its requests through a CORS proxy.
Do WebSockets in a Godot Web export need a CORS proxy?
No. Browser WebSocket connections are not blocked by CORS, though the server may check the Origin header itself. cors.dev proxies HTTP requests only and does not carry WebSockets.
Can a game on itch.io call an http:// API?
Not directly: an https page cannot call plain http:// URLs, because the browser blocks them as mixed content. The proxy accepts http targets behind its https address, so https://proxy.cors.dev/http://... works from an itch.io game.
Which origin do I register for an itch.io game on cors.dev Pro?
https://html-classic.itch.zone, not your https://itch.io page, because itch.io serves HTML5 games in an iframe from that host. With itch.io's SharedArrayBuffer option on, register https://html.itch.zone, and add http://localhost:8060 if you test with Run in Browser.