Setting an Uninstall Survey URL

Use chrome.runtime.setUninstallURL to learn why users remove an MV3 extension: when to set it, what to put in the URL, a two-question survey design, Firefox support and what to do on Safari.

Published October 2, 2026 Updated October 2, 2026 8 min read
Table of Contents

The weekly user count is falling and nobody knows why. Reviews are sparse, support emails are rare, and analytics — if you have them — show what departing users did, not what they thought. The browser offers one moment where you can ask: when the user removes the extension, Chrome and Firefox can open a page you specify. A short, respectful survey on that page is the highest-signal feedback channel most extensions have. It is also easy to misuse, and a few details decide whether it helps or annoys. This guide covers them. It belongs to usage analytics and feature flags.

How the uninstall URL works

chrome.runtime.setUninstallURL(url) registers a URL with the browser. When the user uninstalls the extension, the browser opens it in a new tab — after the extension has been removed, so none of your code runs at that point. The URL is stored by the browser, not by your extension, and persists until you call the method again with a different value or an empty string. Because the extension cannot react at uninstall time, anything the survey page needs to know — version, install age, which features were enabled — must be encoded in the URL in advance and kept current as it changes. The URL is limited to 1023 characters, and anything in it is visible to the user and ends up in your server logs.

Setting and using the uninstall URLThe URL is set on install, refreshed on update and when relevant state changes, stored by the browser, and opened after removal when no extension code can run.installsurvey opensonInstalledset URLUserefresh on changeUninstallcode goneSurvey tabyour sitesetUninstallURL called on installbrowser opens the stored URL
The extension's last chance to shape the URL is before uninstall, not during it.

Step-by-step: a useful, respectful uninstall survey

1. Set the URL on install and update

 1// sw.js
 2chrome.runtime.onInstalled.addListener(() => refreshUninstallUrl());
 3chrome.runtime.onStartup.addListener(() => refreshUninstallUrl());
 4
 5async function refreshUninstallUrl() {
 6  const { installedAt = Date.now(), enabledFeatures = [] } =
 7    await chrome.storage.local.get(["installedAt", "enabledFeatures"]);
 8  const params = new URLSearchParams({
 9    v: chrome.runtime.getManifest().version,
10    age: String(Math.floor((Date.now() - installedAt) / 86_400_000 / 7)),   // weeks installed
11    f: enabledFeatures.slice(0, 5).join(","),
12    b: navigator.userAgentData?.brands?.find((x) => /Chrome|Edge|Brave|Opera/.test(x.brand))?.brand ?? "other",
13  });
14  await chrome.runtime.setUninstallURL(`https://readable.example/goodbye?${params}`);
15}

Execution context: the service worker. Version, install age in weeks, a handful of enabled features and the browser brand are enough to segment responses meaningfully. Install age is the single most useful parameter: “uninstalled within an hour” and “uninstalled after a year” are different problems. Refresh on startup as well as install so the age and features stay current. Store installedAt on first install in onInstalled.

2. Keep identifiers out of the URL

1// Do NOT include:
2//   installId, email, account id, page URLs, search terms, anything typed

Execution context: a code review rule. The survey answers do not need to be linked to an individual’s usage history, and the URL appears in the browser’s history, the new tab’s address bar, and your server logs. If you must correlate (for example to delete an account’s server data on uninstall), use a short-lived random token the server can map, and say so in the privacy policy.

3. Design a two-question survey

 1<!-- https://readable.example/goodbye -->
 2<form method="post" action="/goodbye">
 3  <fieldset>
 4    <legend>Why did you remove Readable? (optional)</legend>
 5    <label><input type="radio" name="reason" value="expectations"> It wasn't what I expected</label>
 6    <label><input type="radio" name="reason" value="permissions"> I was uncomfortable with its permissions</label>
 7    <label><input type="radio" name="reason" value="performance"> It slowed down my browser</label>
 8    <label><input type="radio" name="reason" value="alternative"> I found something better</label>
 9    <label><input type="radio" name="reason" value="temporary"> I only needed it briefly</label>
10    <label><input type="radio" name="reason" value="other"> Something else</label>
11  </fieldset>
12  <label>Anything we could have done better? <textarea name="detail" maxlength="1000"></textarea></label>
13  <button>Send</button>
14</form>

Execution context: a page on your website — not an extension page, which no longer exists. One multiple-choice question and one optional free-text box gets far more responses than a long form. Each choice maps to an action: “expectations” points at the store listing, “permissions” at the install warning, “performance” at profiling. The hidden URL parameters are submitted with the form from location.search.

What each uninstall reason tells you to fixDecision tree mapping common uninstall survey answers to the part of the product to investigate: listing and onboarding, permissions, performance, or competition.Most common reason this month?expectationsListing + onboardingpromise vs productRewrite listingclearer first runpermissionsInstall warningtoo broadactiveTab, optionalnarrow hostsperformanceRuntime costcontent scripts, workerProfile + fixtask manageralternativeCompetitionfeature gapRead free textname the gap
A survey is only useful if each answer points to something you can change.

4. Show something useful even without the survey

The goodbye page is seen by every uninstalling user, not just those who answer. Use it to help: a link to reinstall if it was accidental, instructions for exporting data they may want to keep, and — if the extension had server-side data — a clear link to delete it.

1// goodbye.js on your site
2const params = new URLSearchParams(location.search);
3if (Number(params.get("age")) === 0) {
4  document.querySelector("#quick-exit").hidden = false;     // "Uninstalled right away? Here's what it does…"
5}

Execution context: your website. Tailoring the message to install age is a light touch with a large effect: very early uninstalls often come from users who misunderstood what the extension does, and a two-line explanation sometimes brings them back.

5. Clear the URL when appropriate

1// For example, in an enterprise build where surveys are inappropriate
2if (await isManagedInstall()) await chrome.runtime.setUninstallURL("");

Execution context: the service worker. An empty string removes the uninstall URL. Managed deployments — where an administrator, not the user, removes the extension from many machines — should not open survey tabs on every machine. Detect them through chrome.storage.managed settings or the installType from chrome.management.getSelf() ("admin" for policy installs).

Uninstall URL supportSupport for setUninstallURL, URL length limit and behaviour on policy removal in Chrome, Firefox and Safari.AspectChrome / EdgeFirefoxSafarisetUninstallURLYesYesNoURL limit1023 charsSimilar—Opens on policy removalYes (clear it)Yes—Alternative——In-app feedback
Chrome and Firefox open the page; Safari users need another feedback channel.

6. Close the loop on what you learn

Survey answers only matter if they change something. Review them on a fixed schedule — weekly is common — grouped by version and install age, and track the top reason over time. When you ship a change aimed at a reason (a clearer listing, a narrower permission, a faster content script), the share of uninstalls citing that reason should fall in the following weeks. If it does not, the change did not address what users meant.

1SELECT version, reason, COUNT(*) AS n
2FROM uninstall_survey
3WHERE submitted_at >= now() - interval '28 days'
4GROUP BY version, reason
5ORDER BY version DESC, n DESC;

Execution context: your backend. Splitting by version shows whether a release moved the distribution; splitting by install age separates first-hour churn (listing and onboarding problems) from long-term churn (performance, competition). Free-text answers are often the most valuable part — read them, tag them, and quote them in planning.

Common mistakes

  • Setting it only on first install. Version and age go stale; refresh on startup and update.
  • Long surveys. Response rates fall sharply after the first question. Two questions, both optional.
  • Identifying users in the URL. It lands in history and logs, and contradicts most privacy policies.
  • Guilt-tripping copy. “Why are you leaving us?” with a sad mascot gets fewer honest answers than a neutral, brief page.
  • Forgetting managed installs. An admin removing the extension from a thousand machines opens a thousand survey tabs.

Cross-browser variation

  • Chrome / Edge: chrome.runtime.setUninstallURL with a 1023-character limit; must be http or https.
  • Firefox: browser.runtime.setUninstallURL with the same behaviour. AMO policy expects the page not to collect personal data without consent.
  • Safari: not supported. Add a “Send feedback” link in the options page and in the containing app instead.

Verification

  1. Install the extension, open the service worker console, and call refreshUninstallUrl(); there is no getter, so log the URL you set.
  2. Uninstall from chrome://extensions: a new tab opens at the goodbye page with the expected parameters.
  3. Confirm the URL contains no identifier and is under 1023 characters.
  4. Submit the survey and confirm your backend stores the reason with version and age, and no IP address.

FAQ

Does the uninstall page open if the browser is closed when the extension is removed?

Removal through the browser UI happens while it is open. Removal by deleting the profile or by policy may not open it, or may open it on next launch.

Can I run code when the user uninstalls?

No. There is no uninstall event for the extension itself. Server-side cleanup must be triggered by the goodbye page or by inactivity.

What response rate should I expect?

A small minority of uninstalling users answer, so treat percentages as directional and read the free text closely.

Other Testing, Debugging & Performance Optimization Resources