Appearance
How to add analytics to a Manifest V3 extension
Analytics in a browser extension is a different job from analytics on a website. A website loads a script from an analytics provider and lets it run. A Manifest V3 extension cannot do that, its background code is stopped whenever it is idle, and both stores ask you to declare what you collect before they publish you. This article covers what that means in practice, whatever analytics you use.
Why the usual analytics snippet does not work
Manifest V3 requires that an extension ship all of its own code. In Chrome's words, remotely hosted code is "anything that is executed by the browser that is loaded from someplace other than the extension's own files", and "with Manifest V3 extensions now need to bundle all code they are using inside the extension itself" (Chrome: remote hosted code).
A typical website analytics snippet is exactly that: a small loader that fetches the real script from the provider's server. Put one in an extension page and you have remote code, which is grounds for rejection, and the Chrome Web Store asks you to state on the privacy practices form whether you use any (Chrome: privacy fields).
That leaves two options, and both are fine:
- Bundle an analytics library into your extension as an ordinary dependency, so it ships inside the package and is reviewed with the rest of your code.
- Send plain HTTP requests yourself to an endpoint you control or a service that accepts them. The code that sends is yours and is bundled; only data travels.
Either way, what leaves the extension is data, never code.
Send from the background, not from every page
An extension has several places where code runs: the background service worker, popups and options pages, and content scripts injected into websites. Send analytics from the background.
- Content scripts run inside other people's pages. A request made from one can carry that page's origin rather than your extension's, and it runs in a context you do not control. Pass the event to the background with a message instead.
- Popups close the moment the user clicks away. A request still in flight when that happens may never complete.
- The background is the one place that outlives a single page, so it is where a queue of events can live and be sent in batches.
The service worker stops when it is idle
In Manifest V3 the background is a service worker, and Chrome stops it to save resources. According to Chrome's documentation it is terminated "after 30 seconds of inactivity", when a single event or API call takes longer than 5 minutes, or when a fetch() response takes more than 30 seconds to arrive. Receiving an event or calling an extension API resets the timer, and an incoming event wakes it again (Chrome: service worker lifecycle).
For analytics this has three consequences:
- Do not keep the queue in memory. Anything held in a variable is lost when the worker stops. Write events to
chrome.storage(orbrowser.storagein Firefox) as they happen, and remove them only once a send has succeeded. - Send on startup as well as on a timer. A worker that was stopped mid-batch should pick up where it left off the next time it starts, without sending anything twice.
- Do not block startup. Register listeners at the top level of the worker, synchronously. Chrome will not run a service worker that uses top-level
await, so an analytics library that must be awaited before anything else can stop your whole background from running.
Retry failed sends with a delay that grows, and cap the queue, so an extension that is offline for a week neither hammers a server nor fills the user's disk.
What to send, and what to leave out
The less you collect, the less you have to declare, protect and justify to a reviewer.
- Events you name, such as
extension_installed,popup_openedorexport_clicked, with a few properties that describe what happened. Not the contents of the page, the URL the user was on, or anything they typed. - A random identifier per install, generated once and kept in your extension's storage, if you want to count people rather than events. Generate it; never derive it from anything about the device or the person, which is fingerprinting.
- Nothing personal. No names, email addresses or account IDs from other services. If a report needs to separate people, the random identifier already does.
Uninstalls are worth measuring too, and they need a different mechanism, because nothing in the extension runs once it is removed. Chrome's runtime.setUninstallURL sets a page the browser opens on removal, which "may be used to clean up server-side data, do analytics, and implement surveys". It must be an http: or https: address of at most 1023 characters (Chrome: runtime API). Keep it free of personal data: the browser opens it in a normal tab. How to track uninstalls of a browser extension covers it in detail, including the way Firefox differs.
Reaching your endpoint
A service worker request to another server is a cross-origin request. Chrome's documentation describes listing the server in host_permissions (Chrome: cross-origin network requests), which lets the extension reach it whatever headers it sends. The cost is visible: host permissions appear in the install prompt and in the permissions you must justify to the reviewer.
The alternative is an endpoint that answers cross-origin requests itself, with CORS headers. An extension can then send to it like any page can, without a host permission. Whichever you choose, never ask for <all_urls> or a broad host pattern for analytics. A reviewer will ask why, and so will your users.
What the stores ask you to declare
Both stores ask about data before they publish an extension that collects any.
- The Chrome Web Store has a privacy practices tab: a description of your extension's single purpose, a justification for each permission, the remote code question, the kinds of data you collect, certifications about how you use it, and a link to your privacy policy (Chrome: privacy fields). Its Limited Use policy restricts collected data to "providing or improving your single purpose", and forbids selling it or using it for personalised advertising (Chrome: Limited Use).
- Firefox has built-in consent for data collection, declared in the manifest as
data_collection_permissions. It is supported in Firefox for desktop 140 and Firefox for Android 142 and later, and "from November 3, 2025, all new extensions must adopt" it. Usage analytics usually falls undertechnicalAndInteraction, which "cannot be required": the user can turn it on or off while installing (Mozilla: built-in data consent).
Each store has its own article: What to declare on the Chrome Web Store privacy practices tab and Passing Firefox add-on review with analytics. The free store disclosure helper turns what your extension collects into the answers for both stores, whatever analytics you use.
Ask, and respect the answer
Because Firefox makes usage data optional, an extension that sends analytics needs to work, and send nothing, when the user says no. The same switch is worth having on Chrome, which has no built-in equivalent: a setting in your options page that the analytics code checks before every event and every send. When it turns off, stop sending, clear what is queued, and clear the uninstall URL too, so removal does not report someone who opted out.
How AnalyticsTrend does it
AnalyticsTrend is product analytics for extensions and websites, built around the constraints above.
- It is a bundled package,
@analyticstrend/extension. Nothing is fetched at runtime. - It runs in the background service worker.
initBackgroundreturns at once, so it never blocks startup, and content scripts and popups pass their events to it. - Its queue lives in extension storage, survives the worker stopping, resumes without sending anything twice, backs off after a failure, and is capped.
- Uninstall tracking sets the uninstall URL for you, with no personal data in it.
- It needs only the
storagepermission. Its collector answers cross-origin requests, so no host permission is needed. - It respects Firefox's consent, Global Privacy Control, and your own on/off switch.
What it collects is listed in one place, What it collects. To try it, follow Install in a Chrome extension or Install in a Firefox extension, and see Store policy and privacy for how it maps to each store's form.
Checked against Chrome's and Mozilla's documentation on 7 October 2026. Store policies change: check each answer against the current form before you submit.