How to Center a Map on the Visitor's Approximate Location with IP Geolocation

API or service
IP Geolocation API + Map Tiles
Task
Visitor's IP address → approximate city → map at city zoom
Examples
URLMapLibre GLAI
Difficulty
Beginner
Time
10 min

Center a map on the visitor's approximate location

Your page shows a map, and it should open near the visitor instead of on a fixed city or the whole world: for a store locator, a delivery area, or a local search. The IP Geolocation API finds an approximate location for the IP address a request comes from. Called from the browser without an ip parameter, that's the visitor's IP address, and no permission prompt is shown.

Input: nothing from the visitor. The page sends one request when it opens.

Result: a map that opens on the visitor's city at a city zoom, with the city name in a short note. When no location is available, the map keeps its default view.

Task flow: page opens with a default view → GET /v1/ipinfo without ip → location.latitude and location.longitude → MapLibre GL moves the map to [longitude, latitude] at zoom 10 → default view if the request fails or returns no location.

The IP Geolocation API only returns the location. MapLibre GL renders the Geoapify map tiles and sets the center and zoom.

MapLibre GL map at zoom 10 centered on Salt Lake City, Utah, with the note "Approximate location: Salt Lake City, Utah, United States"

The screenshot shows the map for an example visitor, a University of Utah web server (155.100.38.63). Your visitors see their own city.

A geolocation lookup gives a city-level location. For a precise position, ask the visitor through the browser's Geolocation API. For a street address, let the visitor type it.

Plan the city view and the default view

Decide three things before writing code: the zoom for the detected location, the view for the case without a location, and what to do when the visitor needs more than a city.

City zoom: 10. The IP Geolocation API product page says the API "returns a city-level user location that corresponds to map zoom levels 10-11" and returns "city-level location information, not the user's exact street address". The API reference calls the coordinates "approximate". Zoom 10 shows a whole city with its neighbouring towns, so the visitor recognizes the area even when their part of it is off-center. Zoom 11 is closer and still within that range. A higher zoom would suggest a precision that the result doesn't have.

The coordinates look precise, but the location is city-level. The response for the example visitor contains "latitude": 40.7608, "longitude": -111.891. Four decimals look like a street-level point, but the API describes them only as approximate coordinates. Use them as the center of a city view. Don't put a marker on them as "you are here", don't reverse geocode them into an address, and don't draw an accuracy circle: the response has no accuracy or radius field.

Default view. Create the map with a default view first, so it renders while the request is running. This guide uses the whole world (center: [0, 25], zoom: 1.3). If your service covers one country or region, use that region instead. The map stays at the default view when:

  • the request fails: a network error, a timeout, or an HTTP status other than 200;
  • the response has no location field (a field can be missing when there is no data for it).

Precise position and address. When the visitor needs a precise position, for example "show stores near me" on a phone, use the browser's Geolocation API. It asks the visitor for permission first. Start with the IP-based city view and offer a "Use my location" button for the precise position. For a delivery or billing address, let the visitor type it, for example with Address Autocomplete.

Accuracy. IP-based locations are approximate: VPNs and proxies, for example, can make an IP address appear in another city. Let the visitor move the map or search for a place.

Request the visitor's location

Send one GET request from the visitor's browser, without the ip parameter:

https://api.geoapify.com/v1/ipinfo?apiKey=YOUR_API_KEY
Parameter Value Why
apiKey Your API key Required. A missing or invalid key returns HTTP 401
ip Not set Optional. When it isn't set, the API uses the IP address of whoever sends the request. Sent from the browser, that's the visitor's IP address

The API detects the IP address from the request itself, so the page doesn't need to know it. If you call the API from your backend instead, pass the visitor's IP address in ip; otherwise the API locates your server.

Example visitor. The response below is for a University of Utah web server, 155.100.38.63, requested with ip=155.100.38.63 so that the example stays the same for every reader. Without ip, your visitors get the same fields for their own IP address.

https://api.geoapify.com/v1/ipinfo?ip=155.100.38.63&apiKey=YOUR_API_KEY

Reduced response: the fields this guide uses. The names translations, continent, subdivisions, postal, and the other country fields are left out.

{
  "ip": "155.100.38.63",
  "city": { "name": "Salt Lake City" },
  "state": { "name": "Utah" },
  "country": { "name": "United States" },
  "location": { "latitude": 40.7608, "longitude": -111.891 }
}
Field Used for
location.latitude, location.longitude The map center. Approximate coordinates of a city-level location
city.name, state.name, country.name The note "Approximate location: Salt Lake City, Utah, United States"
ip The IP address that was looked up. Without ip in the request, it's the visitor's address

A field can be missing when there is no data for it, so check location before using it and build the note from the name fields that are present. The response has no street, house number, or accuracy field. All response fields are listed in the API reference.

Credits: 1 request = 1 credit. The Free plan includes 3,000 credits per day. See IP Geolocation API pricing.

Center the map with MapLibre GL

The page loads MapLibre GL JS and has two elements: the map container and a short note above the map.

<link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet">
<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>

<div id="map" style="position: absolute; inset: 0;"></div>
<p id="map-note" style="position: absolute; top: 12px; left: 12px;">Detecting your city…</p>

The script creates the map at the default view, requests the visitor's location in parallel, and moves the map to the city when a location arrives:

const apiKey = "YOUR_API_KEY";

// Default view: the whole world. The map starts here and stays here when no location is available.
const defaultView = { center: [0, 25], zoom: 1.3 };
// City-level view: an IP location corresponds to map zoom levels 10–11.
const cityZoom = 10;

const map = new maplibregl.Map({
  container: "map",
  style: `https://maps.geoapify.com/v1/styles/osm-bright/style.json?apiKey=${apiKey}`,
  center: defaultView.center,
  zoom: defaultView.zoom
});
map.addControl(new maplibregl.NavigationControl(), "top-right");

const note = document.getElementById("map-note");

async function getApproximateLocation() {
  // No ip parameter: the API locates the IP address the request comes from, here the visitor's browser.
  const response = await fetch(`https://api.geoapify.com/v1/ipinfo?apiKey=${apiKey}`, {
    signal: AbortSignal.timeout(5000) // don't keep the visitor waiting for the map
  });
  if (!response.ok) throw new Error(`IP Geolocation request failed with status ${response.status}`);

  const ipInfo = await response.json();
  // A field can be missing when there is no data for it.
  if (!ipInfo.location) return null;

  return {
    // MapLibre expects [longitude, latitude].
    center: [ipInfo.location.longitude, ipInfo.location.latitude],
    name: [ipInfo.city?.name, ipInfo.state?.name, ipInfo.country?.name].filter(Boolean).join(", ")
  };
}

function showDefaultView() {
  note.textContent = "Couldn't detect your city. Showing the world map.";
}

getApproximateLocation()
  .then(location => {
    if (!location) return showDefaultView();
    map.flyTo({ center: location.center, zoom: cityZoom, duration: 2000 });
    note.textContent = `Approximate location: ${location.name || "your area"}`;
  })
  .catch(error => {
    // Network error, timeout, or HTTP error status: keep the default view.
    console.warn(error.message);
    showDefaultView();
  });

Coordinate order. The API returns location.latitude and location.longitude as separate fields. MapLibre GL and GeoJSON expect [longitude, latitude], so the center for the example visitor is [-111.891, 40.7608]. With the values swapped, -111.891 would be read as a latitude, which must be between -90 and 90.

What the code does:

  • The map renders at the default view right away. The request starts at the same time and doesn't wait for the map load event, because flyTo() works before the style has loaded.
  • flyTo() animates from the world view to the city at zoom 10. Use jumpTo() instead if you don't want the animation.
  • AbortSignal.timeout(5000) stops the request after 5 seconds. A timeout, a network error, and an HTTP error status all end in catch and keep the default view.
  • The note tells the visitor which city the map shows and that it's approximate, so a wrong guess doesn't look like an error of the map.
  • The map has no marker on the location: the result is a city, not a point (see Plan the city view).

Attribution. The Geoapify style.json adds the map attributions, and MapLibre GL shows them in the corner: "Powered by Geoapify | © OpenMapTiles © OpenStreetMap contributors". Keep the attribution control. The Geoapify credit is required on the Free plan; see Map Tiles attribution.

The code was tested with MapLibre GL JS 5.24.0 and the Geoapify osm-bright map style. For the screenshot in this guide, the request was sent with ip=155.100.38.63, the example visitor, so that the image doesn't depend on where it was rendered.

Handle errors and prepare for production

Every failure ends the same way for the visitor: the map stays at the default view and the page keeps working. Log the reason for yourself.

Case What you get What the page does
Location found HTTP 200 with location Fly to the city at zoom 10
No location in the response HTTP 200 without location (a field can be missing when there is no data for it) Keep the default view
Missing or invalid API key HTTP 401 Keep the default view; fix the key
Invalid ip value (only when you send ip) HTTP 400; the message field explains the problem Keep the default view
Rate limit exceeded HTTP 429 Keep the default view; don't retry in a loop
Server error HTTP 500 Keep the default view
Network error or timeout fetch() rejects, or AbortSignal.timeout() aborts it Keep the default view

The status codes are listed in the API reference. Error responses have a JSON body with statusCode, error, and message, for example:

{ "statusCode": 401, "error": "Unauthorized", "message": "Invalid apiKey" }

One request per visit. Each request costs 1 credit. The visitor's city doesn't change from page to page, so keep the result in sessionStorage for the visit instead of requesting it on every page view:

async function getApproximateLocationOncePerVisit() {
  try {
    const saved = sessionStorage.getItem("approximateLocation");
    if (saved) return JSON.parse(saved);
  } catch {} // storage can be unavailable, for example in some private windows

  const location = await getApproximateLocation();
  if (location) {
    try { sessionStorage.setItem("approximateLocation", JSON.stringify(location)); } catch {}
  }
  return location;
}

sessionStorage is cleared when the visitor closes the tab, so the next visit asks the API again. Cache only successful results, so a failed request is tried again on the next page.

API key. The request runs in the browser, so the key is visible in the page. Restrict it to your website's HTTP referrers or origins in Geoapify MyProjects. If you call the API from your backend instead, pass the visitor's IP address in ip; without it, the API locates your server.

Let the visitor correct the view. The map is a starting point. Keep panning, zooming, and search available, and offer a "Use my location" button that calls navigator.geolocation.getCurrentPosition() when a precise position is needed. The browser asks the visitor for permission before it shares the position; see the W3C Geolocation specification.

Visitor-centered map with an AI coding agent

Copy this prompt into Codex, Claude Code, Cursor, or another coding agent to open the map in your project at the visitor's approximate city.

Open the map in my project at the visitor's approximate, city-level location from the Geoapify IP Geolocation API, with a default view as the fallback.

Before you start
- Inspect the existing project first. Reuse its current framework, map library, components, styling, HTTP client, and conventions. Make the smallest necessary changes.
- Ask only what the project does not answer: which map should open at the visitor's city, which default view to use (the whole world, or the country or region the service covers), and whether the request runs in the browser or through the project's backend.
- Read the Geoapify API key from the project's environment configuration. Never hardcode, print, or log it.

API request
- Browser: GET https://api.geoapify.com/v1/ipinfo?apiKey=... without the ip parameter. The API then uses the IP address the request comes from: the visitor's.
- Backend: pass the visitor's IP address in the ip parameter; without it, the API locates the server.
- Use location.latitude and location.longitude for the map center, and city.name, state.name, and country.name for a short note such as "Approximate location: Salt Lake City, Utah, United States". Any field can be missing.
- Credits: 1 request = 1 credit. Request it once per visit and keep a successful result in sessionStorage.

Map
- Create the map at the default view first, so it renders while the request is running.
- On success, move the map to the location at zoom 10 (the location is city-level and corresponds to map zoom levels 10-11). The map library expects [longitude, latitude] if it is MapLibre GL or GeoJSON-based; Leaflet expects [latitude, longitude].
- Don't add a "you are here" marker or an accuracy circle: the coordinates are an approximate, city-level location and the response has no accuracy field.
- Keep the map attribution visible (Powered by Geoapify, © OpenMapTiles, © OpenStreetMap contributors).

Fallback and errors
- Keep the default view when the request fails (network error, a 5-second timeout, HTTP 401, 429 or 500) or when the response has no location. Don't retry in a loop, and don't block the page.
- If the project needs a precise position, add a separate "Use my location" action with the browser Geolocation API, which asks the visitor for permission. Street addresses come from the visitor's input, not from this request.
- Add a test with mocked responses: one with a location, one without location, and one with HTTP 401.

Review the generated code against this guide. Check that the browser request has no ip parameter, that the center is built in the order the map library expects, that every failure keeps the default view, and that the API key is not hardcoded.