Getting Geolocation in an MV3 Extension
Read the user's location from an MV3 extension: why the service worker cannot call navigator.geolocation, the geolocation permission, an offscreen document with the GEOLOCATION reason, caching, accuracy and privacy.
Table of Contents
A weather extension, a local-time helper, a store finder: each needs the user’s approximate location. In a web page you call navigator.geolocation.getCurrentPosition. In an MV3 service worker that call does not exist — workers have no geolocation API — and calling it from a content script asks the website’s origin for permission, prompting the user on every site and sharing the result with the page’s context. The extension’s own path to location runs through an offscreen document with the GEOLOCATION reason, backed by the geolocation permission in the manifest. This guide sets it up and covers caching, accuracy and the privacy obligations that come with it. It belongs to offscreen documents and DOM access.
Where geolocation is available
The Geolocation API is exposed on navigator in documents — windows, iframes, extension pages — but not in service workers. An extension page such as the popup can call it, but the popup closes within seconds and a location request can take longer, especially for high accuracy. A content script can call it, but the permission and the prompt belong to the page’s origin, which means asking the user on each site and exposing your use of location to that site. An offscreen document is an extension page with no UI that the service worker can create on demand; with the geolocation permission declared in the manifest, the extension’s origin is granted location access, and the offscreen document can request a position without a per-site prompt.
Step-by-step: location through an offscreen document
1. Declare the permissions
1{
2 "permissions": ["offscreen", "geolocation", "storage"]
3}
Execution context: the manifest. Declaring geolocation grants the extension origin access to the Geolocation API, and Chrome shows “Detect your physical location” at install. Because the warning reduces installs for users who never use location features, consider making it optional and requesting it when the user enables a location-based feature. The operating system may still need to allow the browser itself to use location services.
2. Create the offscreen document when needed
1// sw.js
2async function ensureGeoDocument() {
3 const docs = await chrome.runtime.getContexts({ contextTypes: ["OFFSCREEN_DOCUMENT"] });
4 if (docs.length) return;
5 await chrome.offscreen.createDocument({
6 url: "offscreen/geo.html",
7 reasons: ["GEOLOCATION"],
8 justification: "Get the user's location for local weather",
9 });
10}
Execution context: the service worker. Only one offscreen document may exist; if your extension already keeps one for another purpose, include GEOLOCATION in its reasons instead. The justification string is shown nowhere to users but documents intent for review.
3. Request the position in the document
1// offscreen/geo.js
2chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
3 if (msg?.target !== "geo") return;
4 navigator.geolocation.getCurrentPosition(
5 ({ coords, timestamp }) => sendResponse({ ok: true, lat: coords.latitude, lon: coords.longitude, accuracy: coords.accuracy, at: timestamp }),
6 (err) => sendResponse({ ok: false, code: err.code, message: err.message }),
7 { enableHighAccuracy: false, timeout: 15_000, maximumAge: 10 * 60_000 },
8 );
9 return true;
10});
Execution context: the offscreen document, an extension page on the extension origin. enableHighAccuracy: false uses network-based location, which is faster, uses less power and is precise enough for weather or time zones. maximumAge lets the browser return a recent cached position. Error codes: 1 permission denied, 2 position unavailable, 3 timeout — each deserves its own message to the user.
4. Cache positions and coarsen them
1// sw.js
2export async function getLocation() {
3 const { geo } = await chrome.storage.session.get("geo");
4 if (geo && Date.now() - geo.at < 30 * 60_000) return geo;
5 await ensureGeoDocument();
6 const res = await chrome.runtime.sendMessage({ target: "geo", op: "current" });
7 if (!res?.ok) throw Object.assign(new Error(res?.message ?? "location failed"), { code: res?.code });
8 const coarse = { lat: Math.round(res.lat * 100) / 100, lon: Math.round(res.lon * 100) / 100, at: Date.now() };
9 await chrome.storage.session.set({ geo: coarse });
10 chrome.offscreen.closeDocument().catch(() => {});
11 return coarse;
12}
Execution context: the service worker. Caching for thirty minutes avoids repeated requests; storage.session keeps the location in memory and clears it when the browser exits. Rounding to two decimal places (roughly a kilometre) is plenty for weather and reduces the sensitivity of anything you send to an API. Closing the document after use frees resources; the next request recreates it.
5. Avoid location when a cheaper signal works
1const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // "Europe/Copenhagen"
2const locale = chrome.i18n.getUILanguage(); // "da"
Execution context: any extension context. Time zone and locale are available without any permission and often answer the real question — “what time is it for the user”, “which units to show”. Requesting physical location when the time zone would do adds an install warning and a privacy obligation for no benefit.
6. Handle denial and unavailability gracefully
1// popup.js
2try {
3 const loc = await chrome.runtime.sendMessage({ type: "location:get" });
4 renderWeather(loc);
5} catch (err) {
6 if (err.code === 1) renderManualCity("Location access is off. Enter a city instead.");
7 else renderManualCity("Couldn't get your location. Enter a city instead.");
8}
Execution context: the popup. Users may deny location in the OS, in browser settings, or by managed policy. A manual fallback — type a city — keeps the feature usable and is often preferred by privacy-minded users anyway. Remember the manual choice so you stop asking.
7. Disclose location use
Location is sensitive data in every store’s policy. State in the listing and privacy policy why the extension needs it, whether it leaves the device, which precision you send to any API, and how long it is kept. Never sell or share it, never collect it in the background without a user-visible feature, and never send it alongside browsing data.
Common mistakes
- Calling geolocation in the service worker. It does not exist there.
- Requesting location from a content script. Prompts per site and exposes use to pages.
- High accuracy by default. Slower, more battery, rarely needed.
- Storing precise coordinates. Round and cache in session storage.
- No fallback. Denied users lose the feature entirely.
Cross-browser variation
- Chrome / Edge: offscreen document with
GEOLOCATIONreason and thegeolocationpermission. - Firefox: no offscreen API; the event-page background has
navigator.geolocation, with thegeolocationpermission granting access. - Safari: no offscreen API; call geolocation from an extension page while it is open, or obtain location in the containing app via native messaging.
Verification
- Grant the permission and call
getLocation()from the worker console: a rounded position returns and the offscreen document closes. - Call it again within thirty minutes: served from cache with no document created.
- Deny location in the OS and confirm the popup shows the manual-city fallback.
- Confirm any API request carries only rounded coordinates.
FAQ
Does the user see a prompt?
With the manifest permission, usually not from the browser; the OS may prompt the browser the first time. Optional permissions show Chrome’s permission dialog when requested.
Can I watch position changes continuously?
watchPosition works in the offscreen document, but keeping it open drains battery and keeps the document alive. Extensions rarely need it.
Is IP-based location an alternative?
Your server can estimate a city from the request IP without any permission, which is enough for many features — disclose it the same way.
Does the cached position need clearing when the user travels?
The thirty-minute cache handles ordinary movement. Offer a “Refresh location” button for travellers, and clear the cache when the user changes the manual city setting.
Related
- Choosing the right offscreen reason — GEOLOCATION among the reasons.
- Messaging between offscreen documents and the worker — the request pattern.
- Writing a privacy policy for an extension — disclosing location use.
- Offscreen documents and DOM access — the parent topic.