Browser StudioBrowser Studio
August 8, 2026·7 min read·UserFeed

chrome.runtime.setUninstallURL: Uninstall Feedback

A step-by-step guide to chrome.runtime.setUninstallURL — the Chrome API that opens a page when users uninstall, so you can finally ask why and reduce churn.


chrome.runtime.setUninstallURL is the only built-in hook Chrome gives extension developers to hear from users who are leaving. Call it once, point it at a page you control, and Chrome opens that page in a new tab the moment someone removes your extension. It's a small API — one function, one string argument — but it's the difference between guessing why your uninstall rate looks the way it does and actually knowing.

What setUninstallURL does

chrome.runtime.setUninstallURL(url, callback?) tells Chrome: "when this extension is uninstalled, open url." Chrome stores the URL and fires the navigation automatically at uninstall time — you don't get a chance to run any more of your own code, no cleanup handler, no final API call. The URL itself has to do the work.

That makes it the single moment of attention you get from a departing user. They've already decided to leave. There's no retention flow to run here, no "wait, don't go" modal — that ship has sailed. What you have is a fresh tab and a few seconds of goodwill, and the only reasonable thing to spend it on is asking one question: why?

A few constraints worth knowing up front:

  • The URL must use the http: or https: scheme — both are accepted, but use https in practice since the uninstall request can carry query-string context you don't want sent in the clear.
  • It's capped at 1023 characters, including any query string you append.
  • It only fires on uninstall — not on disable, not on update, not on browser close. If a user disables your extension instead of removing it, this listener never runs.
  • It fires once per uninstall event, and only for extensions with a public, unpacked-store presence in most testing setups — in practice, local development with unpacked extensions can behave inconsistently, so verify it against a Web-Store-installed build before you rely on it in production.

Setting it up

In Manifest V3, extensions run in a service worker rather than a persistent background page, so the service worker can be spun down and restarted by Chrome at any time. That means you shouldn't scatter setUninstallURL calls around your code hoping one of them sticks — call it once, from a listener that's guaranteed to run: chrome.runtime.onInstalled.

// background.js (service worker)
chrome.runtime.onInstalled.addListener(() => {
  chrome.runtime.setUninstallURL("https://your-form.example/uninstall")
})

onInstalled fires on first install, on extension update, and on Chrome update — which is exactly what you want, because it means the uninstall URL gets re-registered every time the service worker's context resets. You don't need to call it anywhere else, and you don't need to call it more than once per onInstalled event.

One thing to double check: register the service worker correctly in your manifest.

{
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  }
}

If setUninstallURL never fires in testing, the most common cause isn't the API — it's that onInstalled didn't run, usually because the service worker was reloaded outside of a real install/update event. Reload the extension from chrome://extensions (not just refresh the page) to trigger a fresh onInstalled call.

Passing context to the page

A bare URL tells you that someone left. It doesn't tell you which version they were on, what language their browser was set to, or anything else that might explain the uninstall. You get one page load — use the query string to carry whatever context you'll want later, before that user is gone for good.

chrome.runtime.onInstalled.addListener(() => {
  const manifest = chrome.runtime.getManifest()
  const params = new URLSearchParams({
    version: manifest.version,
    lang: navigator.language,
    // add your own extension/user id here if you already track one
  })

  chrome.runtime.setUninstallURL(
    `https://your-form.example/uninstall?${params.toString()}`
  )
})

chrome.runtime.getManifest().version gives you the version string straight from manifest.json — useful for spotting whether a specific release caused a spike in uninstalls. navigator.language is available in the service worker context and gives you the browser's locale, which is a cheap proxy for whether a translation or region-specific issue is driving the churn.

Keep the total URL under the 1023-character cap — a handful of short key/value pairs is fine, but don't try to serialize a full user profile into the query string. If you need richer context, pass an opaque id and look up the details server-side instead.

Designing the uninstall page

The page this URL points to is a normal web page — you control it completely, which means it's easy to overbuild. Resist that. The best-performing uninstall pages share a few traits:

One question. Not a survey. "Why are you leaving?" with a handful of structured reasons is enough. Every additional question is a reason not to answer any of them.

Structured reasons first, free text second. Give users a short list to pick from — it had a bug, it was missing a feature, it slowed down the browser, they found something better, they no longer need this kind of tool — and follow it with an optional text field for anything more specific. The checkbox tells you the category; the text field tells you what to actually go fix.

No guilt trips. Don't ask them to reconsider, don't ask for a second chance, don't make the page about winning them back. They've already uninstalled. A page that reads as a retention pitch gets closed immediately; a page that reads as a genuine two-second question gets answered.

Mobile-friendly, fast-loading. In practice, some uninstalls happen on lower-end machines or through automated cleanup tools, and the page needs to render and be usable in under a second on whatever device opens it. No heavy JS bundle, no waiting on a slow API call before the form is interactive.

A short thank-you, nothing more. Once they submit, acknowledge it and stop. "Thanks — this helps us improve" is the whole message.

Doing something with the answers

The value of this API isn't the redirect — it's what you do with the reasons once they start accumulating. A single response is an anecdote. A pattern across dozens of responses is a roadmap.

Once you have a few weeks of data, sort by frequency, not by whichever complaint is loudest in your inbox. If "missing a feature" responses keep naming the same specific feature, that's a build signal. If bug reports cluster around the same flow, that's a fix-now signal. If "found a better alternative" responses keep naming the same competitor, that's worth an afternoon of research into what they're doing differently.

Treat uninstall reasons as one input alongside the rest of your metrics, not the whole picture — pair this data with what your Web Store dashboard and any in-extension analytics are already telling you about install-to-active conversion and feature usage; we cover instrumenting that broader picture in the guide to Chrome extension analytics. And if you want the deeper reasoning behind why this one API is worth prioritizing before almost any other retention work, see the guide to reducing Chrome extension churn.

Building the form, the endpoint to receive responses, and a dashboard to see the breakdown by reason is a real amount of work for something that's ultimately a single API call away from being useful. UserFeed's uninstall feedback hub is built specifically for this — a hosted form behind your setUninstallURL, with responses aggregated by reason automatically. More broadly, UserFeed covers the rest of the feedback loop — feature requests, in-product surveys, roadmap voting — for extension developers who'd rather spend their time on the product than on feedback infrastructure.

Last updated: August 2026

Keep going

Skip building the uninstall page.

UserFeed gives you a hosted, branded uninstall feedback form and the funnel behind it. One URL, done.

Start free