How to Configure Cross-Origin Resource Sharing (CORS) for Remote Tile Sources in GeoLibre

GeoLibre proxies remote tile requests through a Cloudflare Worker that injects Access-Control-Allow-Origin: * headers, allowing the MapLibre GL client to fetch tiles from external services while restricting access to an allow-listed set of upstream domains.

When the browser-based MapLibre GL client performs fetch() calls for raster tiles from external providers—such as OpenPlanetaryMap, USGS WMS, or OpenAerialMap—it enforces the browser's same-origin policy. Without proper cross-origin resource sharing (CORS) headers, these requests fail silently and the map renders blank. To solve this, GeoLibre routes all remote tile traffic through a Cloudflare Worker that adds CORS headers server-side and validates requests against a strict domain allow-list.

Why Browser CORS Blocks Remote Tiles

Modern browsers block JavaScript fetch() requests to external domains unless the server responds with an Access-Control-Allow-Origin header. Many legacy tile servers and scientific data repositories do not include these headers, causing the MapLibre GL client to reject the response. Because GeoLibre targets multiple deployment environments—including web, Tauri desktop, and Jupyter embeddings—it requires a unified solution that guarantees tiles load regardless of the upstream server's CORS configuration.

The Cloudflare Worker Proxy Architecture

GeoLibre implements a named proxy pattern using a Cloudflare Worker defined in workers/tiles/src/index.ts. The worker intercepts tile requests, fetches the data server-side (where CORS policies do not apply), and re-emits the response with static wild-card CORS headers. This approach caches results at Cloudflare's edge, reducing latency while ensuring browser compatibility.

Security Through URL Allow-Listing

The worker restricts requests to a hard-coded allow-list of trusted upstream domains. In workers/tiles/src/allowlisted-fetch.ts, the TILES_ALLOWED_URL_PREFIXES constant defines which base URLs the worker is permitted to fetch:

export const TILES_ALLOWED_URL_PREFIXES = [
  "https://api.openplanetary.org/",
  "https://landsatlook.usgs.gov/",
  "https:// tiles.openaerialmap.org/",
  // ... additional trusted sources
] as const;

When adding a new remote tile source, you must append its base URL to this array. This prevents the worker from becoming an open proxy, as the fetchAllowlistedUpstream function validates every request against this list before following redirects.

Static CORS Header Injection

The worker attaches CORS headers to every response using the CORS_HEADERS constant defined at line 205 of workers/tiles/src/index.ts:

const CORS_HEADERS = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
  "Access-Control-Max-Age": "86400",
};

Because the worker adds these headers after fetching the tile server-side, the browser receives the necessary permissions to consume the data regardless of the upstream server's configuration.

Step-by-Step Configuration Guide

To configure cross-origin resource sharing for a new remote tile source, you must update the allow-list and create a named route in the worker.

Update the Upstream Allow-List

Add the new tile service's base URL to workers/tiles/src/allowlisted-fetch.ts:

export const TILES_ALLOWED_URL_PREFIXES = [
  // …existing entries…
  "https://mytiles.example.com/",
] as const;

This ensures the worker will only fetch from domains you explicitly trust.

Define a Named Route

Create a route in workers/tiles/src/index.ts that extracts tile coordinates from the URL path and forwards them to the upstream service. Use a regular expression to validate the path pattern and prevent abuse:

// Route definition for /mytiles/<z>/<x>/<y>.png
const MY_TILES_PATH = /^\/mytiles\/(\d{1,2})\/(\d{1,7})\/(\d{1,7})\.png$/;

// Inside the request handler:
if (MY_TILES_PATH.test(pathname)) {
  const match = pathname.match(MY_TILES_PATH)!;
  const [, z, x, y] = match;
  const upstream = `https://mytiles.example.com/tiles/${z}/${x}/${y}.png`;
  const upstreamResp = await fetchAllowlistedUpstream(upstream);
  
  // Attach CORS headers to the proxied response
  const headers = new Headers(CORS_HEADERS);
  return new Response(upstreamResp.body, { 
    status: upstreamResp.status, 
    headers 
  });
}

The fetchAllowlistedUpstream function handles the actual HTTP request, enforces the domain allow-list, and manages redirects safely.

Verify CORS Headers

Test the configuration by fetching a tile through the proxy and inspecting the response headers:

fetch('https://tiles.geolibre.app/mytiles/0/0/0.png')
  .then(r => console.log(r.headers.get('Access-Control-Allow-Origin')))
  .catch(console.error);

If configured correctly, the console prints *, confirming the CORS header is present and the browser can consume the tile.

Key Implementation Files

Summary

  • Cloudflare Worker proxy: GeoLibre avoids browser CORS errors by fetching tiles server-side and re-emitting them with Access-Control-Allow-Origin: * headers.
  • Domain allow-listing: The TILES_ALLOWED_URL_PREFIXES array in workers/tiles/src/allowlisted-fetch.ts restricts upstream requests to trusted domains only.
  • Named routing: Route patterns like /mytiles/<z>/<x>/<y>.png validate paths via regex before proxying, preventing open proxy vulnerabilities.
  • Configuration steps: Add the upstream URL to the allow-list, define a route handler using fetchAllowlistedUpstream, and verify responses include the wild-card CORS header.

Frequently Asked Questions

Does GeoLibre support direct tile fetching without a proxy?

Yes. If the remote tile server already sends Access-Control-Allow-Origin: * (or your specific origin), you can bypass the worker. However, the Cloudflare Worker remains the recommended path for consistency across web, Tauri desktop, and Jupyter deployments, as it eliminates dependency on upstream CORS configurations.

What prevents the Cloudflare Worker from becoming an open proxy?

The worker uses named routes combined with the TILES_ALLOWED_URL_PREFIXES allow-list. Before any upstream request executes, the fetchAllowlistedUpstream function validates that the target URL starts with an allowed prefix. Additionally, route regex patterns (e.g., /^\/mytiles\/(\d{1,2})\/(\d{1,7})\/(\d{1,7})\.png$/) ensure only specific tile coordinate patterns trigger proxying.

How do I test if CORS headers are correctly configured?

Open your browser's developer console and execute a fetch() call to your worker endpoint (e.g., fetch('https://tiles.geolibre.app/mytiles/0/0/0.png')). Check that the Access-Control-Allow-Origin response header equals *. If the header is missing or the request throws a CORS error, verify that the route handler includes new Headers(CORS_HEADERS) when constructing the response.

Do desktop builds require CORS configuration?

Tauri-based desktop builds ignore CORS policies because they use native HTTP clients rather than the browser's fetch() implementation. However, you should still route requests through the Cloudflare Worker to maintain code parity between web and desktop deployments and to leverage edge caching for improved performance.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →