Free GeoIP API with Cloudflare Workers

A nice feature of Google Maps is to display a map of your region whenever you visit This is useful because you likely want to look at a map of something nearby instead of on the other side of the world. The way Google knows your general location is because it knows what IP address you’re requesting the map from and it has a database mapping IP addresses to geographic coordinates. This process is called Geolocation, or more specifically GeoIP lookup.

There are free GeoIP API services out there. Here is an example of an IP lookup and response

const response = await fetch(`${ip}?fields=lat,lon`);

// response JSON
    "lat": 47.6722,
    "lon": -122.1257

But because an IP can only be resolved server side and because browsers do not allow cross-domain requests, you have to query on the server side or use a Cloudflare worker.

Or… you can just use Cloudflare workers to do the GeoIP lookup. It turns out that a Cloudflare worker request already has some custom headers containing the user’s latitude and longitude among other things like country and ISP provider. This information is contained in the request object under Here is just an abridged version of what the cf object contains:

"cf": {
  /* Many fields omitted for brevity */
  "longitude": "37.61710",
  "latitude": "55.74830",
  "country": "RU",
  "city": "Moscow",
  "postalCode": "127976"

You can see the full cf fields available by looking at the Cloudflare worker logs.

Using the Cloudflare information is better because it does not require a proxy call to another service, the locations are much more accurate (in my experience), and a lot of free GeoIP services are only free for non-commercial use.

Here is the full source code for a GeoIP lookup worker:

addEventListener("fetch", (event) => {
    const { request } = event;
    // use a POST to prevent crawling
    if (request.method !== "POST") {
        return event.respondWith(new Response('Method not allowed', { status: 405 }));

            (err) => new Response(err.stack, { status: 500 })

async function handleRequest(request) {
    const lat =;
    const lon =;
    const newResponse = new Response(JSON.stringify({ lat, lon }), {
        status: 200,
        statusText: "OK",
        headers: new Headers([
            ['Content-Type', 'application/json; charset=utf-8']

    return newResponse;

I use this Worker to display localized maps on

Thanks for reading.


Now read this

Spring update on Shade Map

A couple of exciting things have happened during the past three months: traffic to the site grew substantially performance improved and new features were added I’ve started to think about a monetising strategy Traffic # Snapshot of live... Continue →