For a few days now, a diagonal “API KEY REQUIRED” watermark has been covering the contact map of thousands of websites. If yours is one of them, nothing changed on your side: CARTO, the basemap provider, changed the rules. Here is what happened, the options left, the one we chose for our own map, and a ready-to-use prompt to fix your sites.

What happened

When you embed an “OpenStreetMap map” on a website, you are actually using two distinct things: the data (OpenStreetMap, free) and the basemap, that is the images or vector tiles that draw this data in a given style. This basemap is served by a host, and for years the most popular host has been CARTO (carto.com), a mapping and location-intelligence company.

CARTO offered three styles for free, with no account and no key, and they became de facto standards: Positron (the very sober light grey basemap, light_all), Dark Matter (dark_all) and Voyager. One URL to paste into Leaflet, one attribution line, done. Thousands of tutorials, WordPress themes and map plugins rely on these basemaps.cartocdn.com URLs.

CARTO has just made an API key mandatory for these basemaps. The stated reason is twofold: a request volume that has become massive, and the impossibility of identifying or contacting abusive users. Without a key the service is not cut off, but every tile is delivered with the “API KEY REQUIRED” watermark. The free allowance is 5 million tiles per month, the key is obtained by e-mail without creating an account, and the measure applies first to raster (PNG) tiles before extending to vector tiles.

In other words: your site has no bug. It depends on a free third-party service whose terms changed overnight, which is the risk inherent to any free third-party service.

How to know whether you are affected

The symptom is unambiguous: grey diagonal text, “API KEY REQUIRED”, repeated across the whole map, sometimes with the mention carto.com/basemaps/apikey. If you want to check without waiting for a client to point it out, two methods:

  • In the browser, open the developer tools, Network tab, filter on cartocdn and reload the contact page. Any request to that domain confirms the dependency.
  • In the site's code (theme, plugins, scripts), search for the string cartocdn.com:

WordPress map plugins are a special case: the CARTO basemap is often picked from a dropdown and the URL never appears in your files. Look in the plugin settings for which “tile provider” or “basemap” is selected.

The options

Get a CARTO key

This is the path of least effort: request a key, add it as a parameter of the tile URL, redeploy. Five million tiles a month is plenty for a showcase website. The downside: you remain dependent on a player who has just demonstrated it can change its terms of use without notice, now with a counter and an identifier attached to your site.

Switch to standard OpenStreetMap tiles

The OpenStreetMap Foundation servers serve the historical style, the one you see on openstreetmap.org, with no key. It is an honest stopgap, but the rendering is very different from a Positron (saturated colors, lots of detail), and the usage policy of these servers explicitly discourages high-traffic sites: they are funded by donations and are not a CDN.

Get the same rendering back with OpenFreeMap

OpenFreeMap (openfreemap.org) publishes vector tiles derived from OpenMapTiles, with several styles including a Positron nearly identical to CARTO's. No key, no quota, no sign-up: the project is funded by donations and its author is one of the OpenMapTiles maintainers. You get the map you had back, with one technical detail: the tiles are vector tiles, so a GL rendering engine is needed to display them. This is the option we chose.

The solution we put in place

Our map was built with Leaflet, like most of the maps we come across. Rather than rewriting everything, we kept Leaflet (custom marker, popup, zoom controls) and replaced only the base layer, thanks to MapLibre GL, the free vector rendering engine, and the official @maplibre/maplibre-gl-leaflet bridge. Two more dependencies, a dozen lines changed:

If your map already runs on MapLibre GL or Mapbox GL, just change the style URL. With OpenLayers, ol-mapbox-style plays the same bridging role. In every case, the OpenFreeMap / OpenMapTiles / OpenStreetMap attribution is mandatory: it is the only thing asked in return.

The result, live (the map below is the one from our contact page, embedded in this article):

Positron basemap served by OpenFreeMap, labels in the site language, no API key.

The pitfall that cost us an hour

First local run: the map is grey. The marker is there, so is the attribution, the console is empty, no network error. The style is downloaded, the sprites too, but not a single tile is requested and map.isStyleLoaded() stays false forever.

The explanation lies in how MapLibre works: tile decoding happens in a Web Worker, and since version 5 the library ships as ES modules only. It creates its worker with new Worker(new URL('./maplibre-gl-worker.mjs', import.meta.url), { type: 'module' }). Some bundlers, Turbopack in our case, intercept this pattern and replace the URL with one of their own chunks, wrapped in their runtime. Loaded in a worker context, that chunk does strictly nothing: it waits for a runtime that does not exist there. The worker never answers, the style never loads, and nothing reports it.

The fix is to take the worker out of the bundler's hands: copy maplibre-gl-worker.mjs and maplibre-gl-shared.mjs (the former imports the latter with a relative path) into the static files folder, then tell MapLibre where to find it before creating the map.

The script runs before every start and every build (target folder ignored by Git, regenerated each time), which avoids freezing a worker version in the repository. Check that your server returns these files as application/javascript.

Labels in the site's language

The Positron style displays place names as name:latin, with an English fallback: you will see “Germany” and “Switzerland” whatever the language of your site. Yet OpenMapTiles tiles carry names in dozens of languages. Once the style is loaded, you only need to rewrite the text-field expression of the name layers, sparing the road number ones:

On a multilingual site, pass the page language: the map then follows the visitor, “Suisse” in French, “Switzerland” in English.

The price to pay

MapLibre GL weighs about 250 KB compressed, plus the shared module reloaded by the worker. That is ten times Leaflet alone. If the map sits at the bottom of the page, only mount it when it approaches the viewport, with an IntersectionObserver and a placeholder of the same height so as not to cause any layout shift. On a contact page this overhead is invisible to the visitor; on a home page it deserves that deferred mount.

The prompt, for those who know what to do with it

If you work with a coding agent (Claude Code or equivalent), here is the prompt we use to apply this fix to an existing site. It is deliberately thorough: inventory before any change, a switch adapted to the library in place, attribution, the worker pitfall, localized labels, performance, and a verification checklist that forbids the agent from declaring the job done without having looked at the result in a browser. Adapt the “Constraints” section to your own rules.

What to take away

A free service without a contract is not a free dependency: it is a dependency whose cost is deferred and whose due date does not depend on you. CARTO has every right to change its terms, and did it cleanly, with a watermark rather than a cut-off. The fact remains that thousands of sites now display a degraded map without anyone on the owner's side having touched anything.

The fix takes an hour when you know the worker pitfall, half a day when you discover it. The result is worth it: the same rendering, labels in your visitors' language, and a fully open chain, from OpenStreetMap data to the rendering engine, with a host whose very model is not to require a key. If your map shows the watermark and you do not know where to start, our contact page has a brand-new map to show you the way.