Use Cases (Updated: 6/2/2026)

Claude Code Geolocation API Guide: Permissions, Fallbacks, Privacy, and Tests

Build Geolocation API features with Claude Code: permission UX, HTTPS, fallback forms, privacy, and tests.

Claude Code Geolocation API Guide: Permissions, Fallbacks, Privacy, and Tests

Location features look simple until they reach real users. A store locator button, a delivery ETA, or a field check-in flow can all start with one browser call, but they also introduce permission prompts, HTTPS requirements, privacy expectations, device-level failures, and map provider boundaries.

That is where Claude Code needs a precise brief. If you ask it to “add current location,” you may get a snippet that calls getCurrentPosition and logs the result. A production implementation should request permission only when the user takes an action, explain why the location is needed, offer manual address or postcode entry, handle timeouts, avoid raw coordinate logs, and prove the flow with mocked browser tests.

This guide uses primary references: MDN Geolocation API, MDN getCurrentPosition, MDN watchPosition, MDN Permissions API, the W3C Geolocation specification, Chrome’s note on secure origins, Chrome DevTools Sensors, Playwright emulation, and Claude Code permissions. For nearby site topics, pair this with map integration, security audit, and responsive design.

Start With Product Boundaries

The Geolocation API asks the browser for the device’s location. The browser may use GPS, Wi-Fi, cell towers, IP hints, operating-system services, or cached data. Your web app does not choose the source, and the result is not guaranteed to be the user’s true physical location.

Before writing code, decide four things. First, request location only at the moment it helps the user. A prompt on first page load feels like tracking. A prompt after “Find stores near me” feels explainable. Second, choose the minimum accuracy needed. A restaurant finder rarely needs high-accuracy GPS; a live field route might. Third, define a fallback. Users can deny permission, run a browser behind enterprise policy, or be indoors with no signal. Fourth, define retention. Most products do not need to store raw latitude and longitude after search results are calculated.

DecisionPractical defaultWhy it matters
Permission timingAfter a user clickBrowsers show a sensitive prompt; context improves trust
AccuracyenableHighAccuracy: false firstHigh accuracy can be slower and more battery-heavy
Failure pathAddress, postcode, or place inputPermission denial is a normal choice, not an exception
LoggingEvents and coarse buckets onlyRaw coordinates are sensitive and hard to clean later

This is also the right time to separate browser location from map integration. Geolocation returns coordinates. Google Maps, Mapbox, OpenStreetMap services, geocoding APIs, route APIs, and store search endpoints are separate systems with separate keys, costs, limits, and privacy rules.

Product Use Cases

The first concrete use case is a store locator. A user clicks “Use my location,” the browser returns coordinates, and your app sorts stores by distance. The safe version also has “search by postcode” next to the button. That fallback protects conversion because users who reject location can still continue.

The second use case is delivery or service availability. A grocery app, repair dispatch app, or rental service can estimate whether a user is inside a service area and show the next available delivery window. Store only the decision you need, such as inside_zone: true or distance_bucket: "0-5km", unless you have a clear reason to retain exact coordinates.

The third use case is field check-in. Cleaning crews, event staff, maintenance teams, and sales reps may need to confirm they are near a site. This should be a controlled action with visible start and stop states. If location fails, use a supervisor approval, photo evidence, or manual note so the worker is not blocked by a device issue.

The fourth use case is regional content. Weather, local alerts, available inventory, and nearby events can all use location. For this scenario, precise location is often unnecessary. A saved city, postcode, or coarse IP-based region may be enough and may create less friction than asking for live coordinates.

Copy-Paste getCurrentPosition Demo

Save this as geo-demo.html and serve it from localhost or HTTPS. It demonstrates a permission-first button, timeout handling, cached-position tolerance, privacy-friendly coordinate rounding, and manual fallback.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Geolocation demo</title>
    <style>
      body {
        font-family: system-ui, sans-serif;
        line-height: 1.6;
        margin: 2rem;
      }
      button,
      input {
        font: inherit;
        padding: 0.7rem 0.9rem;
      }
      .panel {
        border: 1px solid #ddd;
        max-width: 36rem;
        padding: 1rem;
      }
    </style>
  </head>
  <body>
    <main class="panel">
      <h1>Find nearby stores</h1>
      <p>
        We use your location only to rank nearby stores.
        We do not store your exact address or movement history.
      </p>

      <button id="useLocation" type="button">Use my location</button>
      <p id="status" role="status" aria-live="polite"></p>
      <pre id="result"></pre>

      <form id="manualForm">
        <label for="postcode">Postcode or city</label>
        <input id="postcode" name="postcode" autocomplete="postal-code" />
        <button type="submit">Search manually</button>
      </form>
    </main>

    <script type="module">
      const status = document.querySelector("#status");
      const result = document.querySelector("#result");
      const button = document.querySelector("#useLocation");
      const form = document.querySelector("#manualForm");

      function showManual(reason) {
        status.textContent =
          `${reason}. You can still search by postcode or city.`;
      }

      function onSuccess(position) {
        const { latitude, longitude, accuracy } = position.coords;
        status.textContent = "Location received.";
        result.textContent = JSON.stringify(
          {
            lat: Number(latitude.toFixed(5)),
            lng: Number(longitude.toFixed(5)),
            accuracyMeters: Math.round(accuracy),
          },
          null,
          2,
        );
      }

      function onError(error) {
        const messages = {
          1: "Location permission was denied",
          2: "The device position is unavailable",
          3: "The location request timed out",
        };
        showManual(messages[error.code] ?? "Location is unavailable");
      }

      button.addEventListener("click", () => {
        if (!("geolocation" in navigator)) {
          showManual("This browser does not support Geolocation");
          return;
        }

        status.textContent = "Checking location permission...";
        navigator.geolocation.getCurrentPosition(onSuccess, onError, {
          enableHighAccuracy: false,
          timeout: 8000,
          maximumAge: 60000,
        });
      });

      form.addEventListener("submit", (event) => {
        event.preventDefault();
        const data = new FormData(form);
        status.textContent =
          `Searching near "${data.get("postcode")}".`;
      });
    </script>
  </body>
</html>

The three options are worth understanding. enableHighAccuracy asks the browser to prefer a more accurate result when possible, but it does not guarantee GPS. timeout limits how long the app waits before showing a fallback. maximumAge allows a cached position that is recent enough for the task. A store locator can often accept one minute of cache; a check-in button may need fresher data.

Watch Position Without Leaking Work

Use watchPosition for repeated updates, not for a one-time search. It returns an ID that must be passed to clearWatch. Forgetting that cleanup is one of the most common bugs: the UI changes screen, but the browser keeps delivering updates.

import { useEffect, useRef, useState } from "react";

type LocationPoint = {
  lat: number;
  lng: number;
  accuracy: number;
  at: string;
};

export function TrackingPanel() {
  const watchId = useRef<number | null>(null);
  const [points, setPoints] = useState<LocationPoint[]>([]);
  const [error, setError] = useState<string | null>(null);

  function start() {
    if (!navigator.geolocation || watchId.current !== null) return;

    watchId.current = navigator.geolocation.watchPosition(
      (position) => {
        const { latitude, longitude, accuracy } = position.coords;
        setPoints((current) => [
          {
            lat: Number(latitude.toFixed(5)),
            lng: Number(longitude.toFixed(5)),
            accuracy: Math.round(accuracy),
            at: new Date(position.timestamp).toISOString(),
          },
          ...current.slice(0, 9),
        ]);
      },
      (err) => {
        setError(`Tracking failed with code ${err.code}`);
      },
      {
        enableHighAccuracy: true,
        timeout: 10000,
        maximumAge: 5000,
      },
    );
  }

  function stop() {
    if (watchId.current === null) return;
    navigator.geolocation.clearWatch(watchId.current);
    watchId.current = null;
  }

  useEffect(() => stop, []);

  return (
    <section>
      <button type="button" onClick={start}>Start tracking</button>
      <button type="button" onClick={stop}>Stop</button>
      {error && <p role="alert">{error}</p>}
      <ol>
        {points.map((point) => (
          <li key={point.at}>
            {point.lat}, {point.lng}
            {" / "}
            {point.accuracy}m
          </li>
        ))}
      </ol>
    </section>
  );
}

For field products, keep the watch lifecycle visible. “Start route,” “pause,” and “finish” are clearer than silent background tracking. For consumer products, repeated tracking should be rare and strongly justified. A map preview that only needs the first center point should use getCurrentPosition, not a watcher.

Privacy-Safe Logging

Raw latitude and longitude should not be treated like ordinary debug data. They can end up in console output, analytics events, crash reports, server logs, replay tools, screenshots, and support tickets. Ask Claude Code to log outcomes and coarse buckets instead.

type GeoLogInput = {
  lat: number;
  lng: number;
  accuracy: number;
  permission: "granted" | "prompt" | "denied" | "unknown";
};

export function toPrivacySafeGeoLog(input: GeoLogInput) {
  return {
    permission: input.permission,
    accuracyBucket:
      input.accuracy <= 50 ? "high" :
      input.accuracy <= 500 ? "medium" : "low",
    latBucket: Number(input.lat.toFixed(2)),
    lngBucket: Number(input.lng.toFixed(2)),
  };
}

console.info("geo_request_finished", toPrivacySafeGeoLog({
  lat: 40.712776,
  lng: -74.005974,
  accuracy: 38,
  permission: "granted",
}));

Two decimal places can still identify a small area, so treat it as a product decision rather than a universal answer. Many analytics questions only need event names: permission_denied, manual_search_used, timeout, or store_results_shown. If the exact coordinate is not necessary for the user-facing result, do not keep it.

You can query permission state with the Permissions API, but do not overbuild around it. prompt means the user has not made a decision yet. The browser prompt still appears when you call getCurrentPosition or watchPosition, and behavior can vary by browser and device policy.

export async function readGeoPermission() {
  if (!("permissions" in navigator)) return "unknown";

  try {
    const status = await navigator.permissions.query({
      name: "geolocation",
    });
    return status.state;
  } catch {
    return "unknown";
  }
}

Map Integration Boundary

Geolocation does not render a map, geocode an address, or calculate a route. It returns a browser-provided position object. Keep that boundary visible in your codebase.

export type BrowserLocation = {
  lat: number;
  lng: number;
  accuracy: number;
};

export function getBrowserLocation(): Promise<BrowserLocation> {
  return new Promise((resolve, reject) => {
    if (!navigator.geolocation) {
      reject(new Error("Geolocation is not supported"));
      return;
    }

    navigator.geolocation.getCurrentPosition(
      (position) => {
        resolve({
          lat: position.coords.latitude,
          lng: position.coords.longitude,
          accuracy: position.coords.accuracy,
        });
      },
      reject,
      {
        enableHighAccuracy: false,
        timeout: 8000,
        maximumAge: 60000,
      },
    );
  });
}

The map layer should receive BrowserLocation and decide how to center the map or call your own search API. It should not contain permission wording. The browser location module should not contain map provider API keys. This separation makes the feature easier to test and prevents Claude Code from mixing secret management with UI convenience.

Failure Modes to Test

Test HTTP and iframe constraints. Geolocation requires a secure context in modern browsers, and Permissions-Policy can block access in cross-origin frames. If your feature is embedded in a partner portal, the top-level site and iframe allow attribute both matter.

Test permission denial as a normal path. The UI should not shame the user or ask repeatedly. It should move to postcode, city, address, or saved-region input.

Test timeout and unavailable location. Indoor devices, VPNs, desktop computers, disabled OS location services, and enterprise-managed browsers can all produce failures. Show a fallback before the user assumes the app is frozen.

Test stale cache. maximumAge can make a feature feel fast, but it can also use a previous location after the user moves. Use a stricter value for check-ins than for store discovery.

Test logging. Search for latitude, longitude, coords, position, and your analytics event names before shipping. The absence of raw coordinates in logs should be a release criterion.

Automated Testing and Mocking

Chrome DevTools Sensors is useful for exploratory checks. Switch between a known city, a custom coordinate, and “Location unavailable” to verify that the UI does not collapse. For repeatable tests, use Playwright geolocation emulation.

import { expect, test } from "@playwright/test";

test.use({
  geolocation: {
    latitude: 40.712776,
    longitude: -74.005974,
    accuracy: 50,
  },
  permissions: ["geolocation"],
});

test("shows nearby stores from mocked location", async ({ page }) => {
  await page.goto("/stores");
  await page.getByRole("button", { name: "Use my location" }).click();
  await expect(page.getByText("Location received")).toBeVisible();
});

Also keep one test or manual checklist for the denied path. A feature that only passes with pre-granted permission is incomplete. The fallback is not a nice-to-have; it is the conversion path for privacy-conscious users.

Safe Claude Code Prompt

The strongest prompt is not long; it is bounded. Tell Claude Code which files it may touch, which keys and analytics events must remain stable, which data must not be logged, and what evidence you expect after implementation.

claude <<'PROMPT'
Implement a beginner-friendly Geolocation feature.

Scope:
- Edit only src/features/location and related tests.
- Do not change billing, analytics, or map provider config.
- Preserve existing API keys and environment variable names.

Requirements:
- Request location only after the user clicks a button.
- Explain why location is needed before the browser prompt.
- Use getCurrentPosition with timeout and maximumAge.
- Add manual postcode/address fallback for denied or timeout cases.
- Do not log raw latitude or longitude.
- Add a Playwright test with mocked geolocation.
- Return a short verification checklist.
PROMPT

For team workflows, pair prompts with Claude Code permission rules. Deny reading .env files and keep push operations manual until a human reviews the result.

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Bash(git push *)"
    ],
    "allow": [
      "Bash(npm test *)",
      "Bash(npm run lint)"
    ]
  }
}

If you want help turning this into a team checklist, rollout plan, or codebase-specific implementation prompt, the Claude Code training and consultation page is the practical next step. Bring your current map provider, privacy policy constraints, and one real user flow; the work becomes much sharper than a generic snippet.

Hands-On Verification Note

I verified the examples with Chrome on localhost, Chrome DevTools Sensors using a fixed location and “Location unavailable,” and a Playwright test that grants geolocation permission. The success path displayed rounded coordinates, and the failure path moved to manual input. Before using this in production, add checks for OS-level location disabled, enterprise-managed browser permissions, third-party iframe embedding, and your real map provider quota behavior.

#Claude Code #Geolocation #location #maps #Web API
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.