Cloudflare configuration for serving up maps

We support two types of maps access:

  1. Individual tiles
  2. Offline maps which cover larger regions e.g. city/state/country

Individual tiles

The instructions here https://docs.protomaps.com/deploy/cloudflare? detail how to configure a protomaps tile server on Cloudflare. Cloudflare is always the most cost effective host for protomaps as can be seen here https://docs.protomaps.com/deploy/cost.

The basic configuration is that there’s an R2 bucket that contains our single large world map file. This is accessed via a worker which takes in a URL and return the requested tile. The form of the worker URL is:

https://cloudflare-domain-name/WORLD_FILE_NAME/z/x/y.mvt

where WORLD_FILE_NAME is the name of the world map file in the R2 bucket, z is the zoom and x and y are the tile coordinates.

Updating the maps

Periodically we build a new world map and we want to update it on Cloudflare without any service interruption. Here are the proposed steps:

  1. Upload a new world map named by date of production e.g. world-2025-10-08.pmtiles.
  2. Adjust the worker to use the new file

The stock Worker code is https://github.com/protomaps/PMTiles/blob/main/serverless/cloudflare/src/index.ts and it uses url.pathname as the name of the pmtiles file to use - which is WORLD_FILE_NAME in the URL above. Could we instead make this a Workers KV pair in the environment? The Worker would read in the current value of the file to use and off it would go. Step 2 would then simply be updating the value in the KV pair to contain the new filename. The new URL would no longer need the filename to access as that would be set in the KV i.e.

https://cloudflare-domain-name/z/x/y.mvt

This should allow for switching between different maps as required.

Costs

For this to scale we’ll need:

  1. Paid worker plan which is $5 per month
  2. R2 storage costs which are 1.5 cents per Gigabyte and 36 cents for every million GETs over 10 million.

The zoom level 14 map is currently around 75GB so that would cost around $6.50 in total.

Offline maps

Offline map extracts are Protomaps .pmtiles files that cover smaller regions and are generated by extracting them directly from the world map. Every time a new world map is created we generate a new set of extracts from it. We have a Python script to automatically generate GeoJSON to describes the extracts that we want. This uses geonames.org data to create features for countries, states and clusters of cities. A second Python script takes that along with the world map and generates the extracts along with a manifest GeoJSON file which adds the extract sizes to the input GeoJSON.

We currently have 3155 extracts to cover the world and the manifest is around 3MB. These are served up directly from an R2 bucket which is exposed to the Internet via a custom domain. When a user wishes to download offline maps, the app downloads the manifest to allow them to select those which they want to download. The extracts vary in size from a few hundred kilobytes up to several gigabytes.

Updating the maps

When the extracts are regenerated from a new world map we’ll update them on the server in the following way:

  1. Upload all of the extracts to a folder within the bucket named with the date e.g. extracts-2025-10-08.
  2. Upload the manifest.geojson to the same folder.
  3. Update a redirect Rule in Cloudflare so that requests for the manifest.geojson get the one from the latest extracts folder.
  4. For this to work, when accessing the extracts from the manifest it needs to be from the same folder as the manifest.

In this way if a user selecting offline maps will be using the latest manifest.geojson that was available at the point at which they started the process and the files will all be available to it.

Costs

  1. Monthly R2 storage costs which are 1.5 cents per Gigabyte and 36 cents for every million GETs over 10 million.

The number of GETs will be much lower than with the tile server as in general a user will only rarely download an offline map and so the only cost is storage. Because there are overlaps between extracts the current total of extracts based on the 75GB world map is 205GB which would cost $3.08 per month.