analyticsgoogle-analyticschrome-extensionsmanifest-v3

Google Analytics for Chrome Extensions: Why It Breaks (and What to Use Instead)

Adding GA4 to a Chrome extension looks simple — until the numbers come back wrong or empty. Here's why Google Analytics breaks in Manifest V3 extensions, and what actually works.

It's the obvious first move: you want to know how many people use your Chrome extension, so you reach for Google Analytics. It's free, you already know it, and it's on every other site you've built. Then the numbers come back wrong — or empty — and you can't figure out why. Here is the full reason GA4 breaks inside a Manifest V3 extension, and what to use instead.

Can you even use Google Analytics in a Chrome extension?

Partly. You can send events to a GA4 property from an extension — but not the way you do on a website. The normal setup is the gtag.js snippet: a script tag that loads Google's analytics library from googletagmanager.com. That script does the heavy lifting — page views, sessions, the client ID, automatic events.

Inside a Manifest V3 extension, that snippet does not work. Which means everything gtag.js did automatically, you now have to do by hand — and several of those things cannot be reproduced correctly in an extension at all. Let's go through them.

Problem 1 — Manifest V3 bans gtag.js

Manifest V3 prohibits remotely-hosted code. Every script your extension executes has to be bundled inside the extension package and reviewed by the Chrome Web Store. gtag.js is, by definition, a remote script loaded at runtime from Google's servers — so it is not allowed. Drop the standard snippet into a popup or a service worker and it is blocked by the extension content security policy.

You have two ways around it, both with costs:

  • Bundle a copy of gtag.js. Technically possible, but you're now shipping a frozen snapshot of Google's library that never updates, and it still assumes a web-page environment it doesn't have.
  • Use the GA4 Measurement Protocol. This is the realistic option — and the rest of this article is mostly about its limits.

The Measurement Protocol workaround

The GA4 Measurement Protocol is an HTTP endpoint you can POST events to directly — no library required. From an extension service worker it looks like this:

// Sending a GA4 event from a service worker via the Measurement Protocol
await fetch(
  'https://www.google-analytics.com/mp/collect' +
  '?measurement_id=G-XXXXXXX&api_secret=YOUR_SECRET',
  {
    method: 'POST',
    body: JSON.stringify({
      client_id: anonymousId,        // you must generate & persist this
      events: [{ name: 'feature_used', params: { feature: 'export' } }],
    }),
  }
);

This works. Events show up in GA4. But notice what you had to do yourself: generate and persist a client_id, decide what an event is, batch nothing, retry nothing. The Measurement Protocol is a raw pipe — every convenience gtag.js gave you is now your problem. And the deeper issues below it cannot fix at all.

Problem 2 — chrome-extension:// origins confuse GA4

GA4's data model is built around web pages: a hostname, a path, a referrer, a page title. An extension has none of that in a way GA4 understands. Your popup's URL is chrome-extension://<random-id>/popup.html. There is no referrer. A content script runs on someone else's site, so its "page" is that third party's URL — not yours.

The result: sessions and page views get attributed to the wrong context, your reports fill with the extension ID as a hostname, and any standard GA4 report that assumes a website — acquisition, landing pages, referral traffic — is meaningless. You can suppress page views and send only custom events, but at that point GA4 is just an event bucket, and a clumsy one.

Problem 3 — service worker termination drops events

This one is subtle and catches almost everyone. Manifest V3 runs your background logic in a service worker that Chrome terminates after about 30 seconds of idle time. Any analytics code that batches events in memory and flushes them on a setInterval loses the batch when the worker dies — and the setInterval itself dies too, so the flush never runs.

In development, with DevTools open, the worker never sleeps, so it looks fine. In production it sleeps constantly, and a large share of your events silently never send. Nothing errors, because the event was discarded before any network call. You just see a fraction of real usage and assume your extension is less popular than it is.

The Measurement Protocol does not solve this — it's about how you send, not when. Reliable tracking in MV3 requires a storage-backed queue and alarm-based flushing. We wrote a full breakdown of that failure mode in why your service worker keeps stopping — it is the single biggest reason DIY extension analytics under-counts.

Problem 4 — Chrome Web Store policy risk

This is the one that can get your extension rejected or removed, not just produce bad data. The Chrome Web Store requires you to disclose what data you collect, limits use to a single disclosed purpose, and is strict about sending user activity to third parties.

A content script that loads on every site the user visits, with analytics that capture the page URL, is sending the user's browsing history to Google through your extension. Unless that is explicitly disclosed and justified, it is a policy violation. Plenty of extensions have been flagged in review for exactly this — an analytics SDK quietly shipping full URLs upstream. GA4's whole design encourages capturing the "page," which in an extension is someone else's site.

Problem 5 — no install, uninstall, or retention model

Even if you fix everything above, GA4 still doesn't answer the questions that actually matter for an extension:

  • Installs with a source. GA4 has no concept of a Chrome Web Store install. There is no listing-to-install funnel.
  • Uninstalls. GA4 cannot see chrome.runtime.setUninstallURL. The single most important churn signal is invisible to it.
  • Retention cohorts. GA4's retention reporting assumes web sessions. D1/D7/D30 cohorts for an extension audience need install-date anchoring GA4 doesn't do.
  • MV3 health. Service-worker cold starts, terminations, errors scoped to extension and browser version — none of this exists in GA4's model.

You can force some of it with custom events and custom reports, but you're rebuilding an extension analytics tool inside a product designed for websites.

What to use instead

Use analytics built for extensions. Whatever you pick — including building your own — it should clear this checklist:

  • No remote code. The SDK is bundled, MV3-compliant out of the box, and small enough not to hurt cold-start time (under ~10 KB).
  • Survives worker termination. Storage-backed event queue, chrome.alarms-based flushing, synchronous listener registration.
  • Native install & uninstall tracking. Reads chrome.runtime.onInstalled and setUninstallURL, with attribution.
  • Retention cohorts anchored to install date. D1/D7/D30 that mean something for an extension audience.
  • Privacy-safe by default. Anonymous IDs, no fingerprinting, no browsing history shipped upstream — so you stay clear of Chrome Web Store policy trouble.

That checklist is exactly why purpose-built tools exist. Crxlytics is one — a 5 KB MV3-hardened SDK with installs, retention, feature usage, errors, and uninstalls wired by default. If you're comparing options, our analytics tool comparison lays them out side by side, and the guide to tracking extension metrics covers what to measure once your tracking is reliable.

Google's tooling is excellent — for websites. An extension is a different runtime with different constraints, and the honest answer is that GA4 was never built for it.

FAQ

Can I add Google Analytics to a Chrome extension?

You can send events to GA4 via the Measurement Protocol — an HTTP endpoint that needs no library. You cannot use the standard gtag.js snippet, because Manifest V3 bans remotely-hosted code. And even with the Measurement Protocol, GA4 misattributes extension contexts and has no install, uninstall, or retention model.

Why does gtag.js not work in a Manifest V3 extension?

gtag.js is loaded at runtime from Google's servers. Manifest V3 prohibits executing remotely-hosted code — all scripts must be bundled in the extension and reviewed. The extension content security policy blocks the remote script.

Is using Google Analytics in an extension against Chrome Web Store policy?

Not inherently — but it's easy to violate policy with it. If analytics capture the URLs of pages your content script runs on, you are sending the user's browsing activity to a third party. That must be clearly disclosed in your privacy practices and justified by a single purpose, or the extension can be flagged in review.

What's the best analytics tool for a Chrome extension?

Any tool genuinely built for extensions rather than websites — it must be MV3-compliant with no remote code, survive service-worker termination, and track installs, uninstalls, and retention natively. See our comparison of extension analytics tools for specifics.

Does the Measurement Protocol fix the dropped-events problem?

No. The Measurement Protocol only changes how you transmit an event. If your code batches events in memory and the MV3 service worker is terminated before the batch is flushed, the events are lost regardless. Reliable delivery needs a queue persisted to chrome.storage and flushing driven by chrome.alarms.

Analytics built for extensions, not websites
Crxlytics is a 5 KB MV3-native SDK — installs, retention, feature usage, errors, and uninstalls out of the box. No remote code, no dropped events, no policy risk.
Get started free →