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

> Configure CORS for remote tile sources in GeoLibre. Learn how GeoLibre proxies requests and injects headers to securely fetch tiles from external services.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/allowlisted-fetch.ts), the `TILES_ALLOWED_URL_PREFIXES` constant defines which base URLs the worker is permitted to fetch:

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/index.ts):

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/allowlisted-fetch.ts):

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/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:

```typescript
// 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:

```javascript
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

- **[`workers/tiles/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/index.ts)**: The main Cloudflare Worker entry point that routes requests, validates paths, and attaches `CORS_HEADERS` to responses.
- **[`workers/tiles/src/allowlisted-fetch.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/allowlisted-fetch.ts)**: Defines `TILES_ALLOWED_URL_PREFIXES` and the `fetchAllowlistedUpstream` function that enforces domain restrictions.
- **[`packages/plugins/src/plugins/source-coop-api.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/source-coop-api.ts)**: Example plugin implementation that relies on the worker's CORS-enabled proxy for Source Cooperative metadata requests.
- **[`workers/collab/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/collab/src/index.ts)**: Secondary worker demonstrating shared use of the `CORS_HEADERS` constant for collaborative editing features.

## 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`](https://github.com/opengeos/GeoLibre/blob/main/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.