Back to Blog

Itch.io CORS Issues Explained: Fix Cross-Origin Errors in Browser Games

Complete guide to understanding and fixing CORS issues when hosting games on Itch.io. Covers SharedArrayBuffer, external API calls, Unity WebGL, and Godot web exports.

Itch.io CORS Issues Explained: Fix Cross-Origin Errors in Browser Games

Itch.io is one of the most popular platforms for hosting indie games, including browser-based HTML5 and WebGL games. However, developers frequently encounter CORS (Cross-Origin Resource Sharing) issues when their games try to communicate with external servers or require specific browser features. This comprehensive guide explains why these issues occur and how to solve them.

Table of Contents

  1. Understanding Itch.io’s Hosting Architecture
  2. Common CORS Issues on Itch.io
  3. SharedArrayBuffer and Cross-Origin Isolation
  4. Connecting to External APIs
  5. Unity WebGL Games on Itch.io
  6. Godot Web Exports
  7. Using a CORS Proxy
  8. Best Practices and Workarounds

Understanding Itch.io’s Hosting Architecture

When you upload a game to Itch.io, it gets served from their CDN (Content Delivery Network). Your game runs from one of these domains:

  • https://html-classic.itch.zone
  • https://html.itch.zone
  • https://*.itch.zone

Previously, games also loaded from https://v6p9d9t4.ssl.hwcdn.net, but this domain has been permanently disabled.

Why This Matters

Your game’s “origin” for CORS purposes is the Itch.io domain, not your own domain. This means:

  1. Any request to your own servers is a cross-origin request
  2. Requests to third-party APIs are cross-origin requests
  3. The Itch.io API itself doesn’t support browser-side requests

Iframe Embedding Complications

When your game is embedded in an iframe on an Itch.io game page (rather than opened in fullscreen/separate window), additional restrictions may apply due to how iframes interact with cross-origin policies.

Common CORS Issues on Itch.io

Issue 1: External API Calls Blocked

Symptom:

Access to fetch at 'https://api.yourserver.com/scores' from origin
'https://html.itch.zone' has been blocked by CORS policy

Cause: Your backend server doesn’t include the Itch.io domain in its CORS configuration.

Issue 2: Itch.io API Access Denied

Symptom:

No 'Access-Control-Allow-Origin' header is present on the requested resource

Cause: The Itch.io API is designed for server-side access only. It doesn’t include CORS headers because your API key must remain private.

Issue 3: Asset Loading Failures

Symptom: Images, audio, or other assets fail to load with CORS errors.

Cause: External CDNs or asset servers aren’t configured for cross-origin access.

Issue 4: SharedArrayBuffer Not Available

Symptom:

Error: The following features required to run Godot projects on the Web are missing:
- Cross Origin Isolation
- SharedArrayBuffer

Cause: Browser security requirements for multi-threaded WebAssembly.

SharedArrayBuffer and Cross-Origin Isolation

Modern game engines like Godot (3.3+) and Unity use multi-threading in WebAssembly for better performance. This requires SharedArrayBuffer, which browsers only enable under specific security conditions.

Required HTTP Headers

For SharedArrayBuffer to work, the server must send:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Itch.io’s Position

Itch.io has had a complicated relationship with these headers:

  1. They initially enabled the headers for new games
  2. This broke existing games that loaded external resources
  3. They reverted the changes and are working on a solution with two CDN configurations

Current Workarounds

Option 1: Single-Threaded Fallback

Configure your game to fall back to single-threaded mode when SharedArrayBuffer isn’t available:

For Godot:

  • Export with “Thread Support” set to “Optional”
  • The game will run slower but remain functional

For Unity:

  • Build without multi-threading enabled
  • Use WebGL 1.0 compatibility mode if needed

Option 2: Pop-Out Window

Games often work correctly when opened in a separate window (using Itch.io’s “Open in new window” button) rather than playing in the embedded iframe.

Connecting to External APIs

If your game needs to communicate with external servers (leaderboards, multiplayer, analytics), you have several options.

Option 1: Configure Your Server

Update your server’s CORS configuration to allow requests from Itch.io domains:

Node.js/Express:

const cors = require('cors');

const corsOptions = {
  origin: [
    'https://html-classic.itch.zone',
    'https://html.itch.zone',
    /\.itch\.zone$/  // Allow all itch.zone subdomains
  ],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true
};

app.use(cors(corsOptions));

Nginx:

location /api {
    set $cors_origin "";
    if ($http_origin ~* "^https://.*\.itch\.zone$") {
        set $cors_origin $http_origin;
    }

    add_header 'Access-Control-Allow-Origin' $cors_origin always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Content-Type' always;

    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

Option 2: Use a CORS Proxy

When you can’t modify the target server, route requests through a CORS proxy like corsproxy.io:

JavaScript Example:

const CORS_PROXY = 'https://corsproxy.io/?url=';

async function fetchLeaderboard() {
    const apiUrl = 'https://api.leaderboards.com/scores';
    const response = await fetch(CORS_PROXY + encodeURIComponent(apiUrl));
    return response.json();
}

Option 3: Embed Data in Your Build

For static data (levels, configurations), consider embedding it directly in your game files:

// Instead of loading levels.json via fetch
// Include the data as a JavaScript file
const LEVELS = [
    { id: 1, name: "Tutorial", data: [...] },
    { id: 2, name: "Forest", data: [...] }
];

Unity WebGL Games on Itch.io

Unity WebGL builds commonly encounter CORS issues on Itch.io.

Why C# and GDScript Code is Subject to CORS

CORS is a browser security mechanism, so you might wonder why it affects C# (Unity) or GDScript (Godot) code. When you build for WebGL/HTML5, your game code compiles to WebAssembly (WASM) that runs inside the browser. Network requests from UnityWebRequest or Godot’s HTTPRequest use the browser’s fetch/XMLHttpRequest APIs under the hood. The browser enforces CORS on these requests regardless of what language originally made them. This is why the same code works in native builds but encounters CORS errors in web exports.

UnityWebRequest with CORS Proxy

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class ItchIONetworking : MonoBehaviour
{
    private string CorsProxy(string url)
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
            return "https://corsproxy.io/?url=" + UnityWebRequest.EscapeURL(url);
        #else
            return url;
        #endif
    }

    public IEnumerator SubmitScore(int score)
    {
        string url = CorsProxy("https://yourserver.com/api/scores");

        WWWForm form = new WWWForm();
        form.AddField("score", score);

        using (UnityWebRequest request = UnityWebRequest.Post(url, form))
        {
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.Success)
            {
                Debug.Log("Score submitted!");
            }
        }
    }
}

Loading External Assets

public IEnumerator LoadExternalTexture(string imageUrl)
{
    // Always use CORS proxy for external images on Itch.io
    string proxiedUrl = "https://corsproxy.io/?url=" + UnityWebRequest.EscapeURL(imageUrl);

    using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(proxiedUrl))
    {
        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            Texture2D texture = DownloadHandlerTexture.GetContent(request);
            GetComponent<Renderer>().material.mainTexture = texture;
        }
    }
}

Godot Web Exports

Godot games face unique challenges on Itch.io, especially with threading requirements.

HTTP Requests in Godot

extends Node

const CORS_PROXY = "https://corsproxy.io/?url="

func _ready():
    # For web builds, use the CORS proxy
    var url = "https://api.example.com/data"
    if OS.has_feature("web"):
        url = CORS_PROXY + url.uri_encode()

    var http_request = HTTPRequest.new()
    add_child(http_request)
    http_request.request_completed.connect(_on_request_completed)
    http_request.request(url)

func _on_request_completed(result, response_code, headers, body):
    if result == HTTPRequest.RESULT_SUCCESS:
        var json = JSON.parse_string(body.get_string_from_utf8())
        print(json)

Export Settings for Itch.io

  1. In Export settings, set Thread Support to “Disabled” or “Optional”
  2. Enable VRAM Texture Compression for the web
  3. Consider disabling GDExtension if not needed (reduces compatibility issues)

Fallback for Missing Features

func _ready():
    if OS.has_feature("web"):
        # Check if we're running in a restricted environment
        var js_code = """
            (function() {
                return typeof SharedArrayBuffer !== 'undefined';
            })();
        """
        var has_sab = JavaScriptBridge.eval(js_code)

        if not has_sab:
            show_single_thread_warning()

Using a CORS Proxy

A CORS proxy like corsproxy.io acts as an intermediary that adds the necessary CORS headers to responses.

How It Works

  1. Your game sends a request to the proxy
  2. The proxy fetches the resource from the target server
  3. The proxy adds Access-Control-Allow-Origin headers
  4. Your game receives the response without CORS errors

Implementation Pattern

class APIClient {
    constructor(baseUrl, useCorsProxy = false) {
        this.baseUrl = baseUrl;
        this.corsProxy = 'https://corsproxy.io/?url=';
        this.useCorsProxy = useCorsProxy;
    }

    buildUrl(endpoint) {
        const url = this.baseUrl + endpoint;
        if (this.useCorsProxy) {
            return this.corsProxy + encodeURIComponent(url);
        }
        return url;
    }

    async get(endpoint) {
        const response = await fetch(this.buildUrl(endpoint));
        return response.json();
    }

    async post(endpoint, data) {
        const response = await fetch(this.buildUrl(endpoint), {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        return response.json();
    }
}

// Usage - enable proxy for web builds
const isWeb = typeof window !== 'undefined' && window.location.hostname.includes('itch');
const api = new APIClient('https://api.mygame.com', isWeb);

Best Practices and Workarounds

1. Test on Itch.io Early

Don’t wait until release to test your game on Itch.io. Upload a test build early to catch CORS issues before they become critical.

2. Provide Offline Functionality

Design your game to work without network features when CORS issues occur:

async function loadLeaderboard() {
    try {
        const scores = await fetchFromServer();
        displayScores(scores);
    } catch (error) {
        console.warn('Could not load online leaderboard:', error);
        displayLocalScores();
        showOfflineNotice();
    }
}

3. Use LocalStorage for Persistence

Instead of relying on external servers for save data, use browser storage:

function saveGame(data) {
    localStorage.setItem('gameProgress', JSON.stringify(data));
}

function loadGame() {
    const saved = localStorage.getItem('gameProgress');
    return saved ? JSON.parse(saved) : null;
}

4. Consider a Downloadable Version

For games that heavily rely on features blocked by CORS restrictions, offer a downloadable version alongside the web version.

5. Document Known Issues

Be transparent with players. Add a note to your game’s description if certain features require playing in fullscreen mode or downloading the game.

6. Future-Proof Your CORS Configuration

If you control a server, allow the itch.zone domain to cover all current and future Itch.io subdomains:

// This regex matches any itch.zone subdomain
const itchOrigin = /^https:\/\/.*\.itch\.zone$/;

Conclusion

CORS issues on Itch.io stem from the fundamental architecture of web security. While Itch.io continues to work on better solutions for features like SharedArrayBuffer, developers can work around most limitations by:

  1. Configuring their own servers to accept requests from Itch.io domains
  2. Using CORS proxies like corsproxy.io for third-party APIs
  3. Designing games with graceful fallbacks for restricted environments
  4. Embedding static data directly in game builds

By understanding these issues and implementing proper solutions, you can ensure your game works reliably for all players on Itch.io.

Related blog posts

Create a free Account to fix CORS Errors in Production

Say goodbye to CORS errors and get back to building great web applications. It's free!

CORSPROXY Dashboard