Google Analytics 4 in Chrome Extensions: Complete Tracking Guide (2026)

AppBooster Team · · 4 min read
Analytics dashboard showing data charts

Adding Google Analytics 4 to Your Chrome Extension

Understanding how users interact with your extension is essential for growth. Google Analytics 4 provides the insights you need, but Manifest V3 prohibits loading remote scripts — so you can’t use the standard gtag.js snippet.

The solution: GA4’s Measurement Protocol, a server-side API that lets you send events directly from your extension’s code.


Setup: Get Your Credentials

  1. Create a GA4 property at analytics.google.com
  2. Go to AdminData StreamsWeb → Create a stream
  3. Note your Measurement ID (starts with G-)
  4. Go to AdminData Streams → Select stream → Measurement Protocol API secrets
  5. Create a new API secret and save it
{
  "manifest_version": 3,
  "permissions": ["storage"]
}

Core Analytics Module

Create a reusable analytics module for your extension:

// analytics.js
const GA_ENDPOINT = 'https://www.google-analytics.com/mp/collect';
const GA_DEBUG_ENDPOINT = 'https://www.google-analytics.com/debug/mp/collect';
const MEASUREMENT_ID = 'G-XXXXXXXXXX';
const API_SECRET = 'your_api_secret';
const DEBUG = false;

async function getOrCreateClientId() {
  const result = await chrome.storage.local.get('ga_clientId');
  if (result.ga_clientId) return result.ga_clientId;

  const timestamp = Math.floor(Date.now() / 1000);
  const randomPart = Math.floor(Math.random() * 2147483647);
  const clientId = `${randomPart}.${timestamp}`;

  await chrome.storage.local.set({ ga_clientId: clientId });
  return clientId;
}

async function getOrCreateSessionId() {
  const SESSION_TIMEOUT_MIN = 30;
  let { sessionData } = await chrome.storage.session.get('sessionData');

  const now = Date.now();

  if (sessionData && now - sessionData.timestamp < SESSION_TIMEOUT_MIN * 60 * 1000) {
    sessionData.timestamp = now;
    await chrome.storage.session.set({ sessionData });
    return sessionData.sessionId;
  }

  sessionData = {
    sessionId: now.toString(),
    timestamp: now
  };
  await chrome.storage.session.set({ sessionData });
  return sessionData.sessionId;
}

async function sendEvent(eventName, params = {}) {
  const clientId = await getOrCreateClientId();
  const sessionId = await getOrCreateSessionId();

  const endpoint = DEBUG ? GA_DEBUG_ENDPOINT : GA_ENDPOINT;

  const body = {
    client_id: clientId,
    events: [{
      name: eventName,
      params: {
        session_id: sessionId,
        engagement_time_msec: '100',
        ...params
      }
    }]
  };

  const url = `${endpoint}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`;

  try {
    const response = await fetch(url, {
      method: 'POST',
      body: JSON.stringify(body)
    });

    if (DEBUG) {
      const debugResponse = await response.json();
      console.log('GA4 Debug:', debugResponse);
    }
  } catch (error) {
    console.error('GA4 send failed:', error);
  }
}

Tracking Common Events

Extension Installation and Updates

// service-worker.js
chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    sendEvent('extension_installed', {
      version: chrome.runtime.getManifest().version
    });
  } else if (details.reason === 'update') {
    sendEvent('extension_updated', {
      previous_version: details.previousVersion,
      current_version: chrome.runtime.getManifest().version
    });
  }
});
// popup.js
sendEvent('page_view', {
  page_title: 'Popup',
  page_location: 'chrome-extension://popup.html'
});

Feature Usage

// Track button clicks
document.getElementById('analyze-btn').addEventListener('click', () => {
  sendEvent('feature_used', {
    feature_name: 'analyze_extension',
    trigger: 'popup_button'
  });
});

// Track settings changes
function onSettingChanged(settingName, newValue) {
  sendEvent('setting_changed', {
    setting_name: settingName,
    setting_value: String(newValue)
  });
}

Errors and Performance

// Track errors (avoid reserved name "error")
function trackError(context, message) {
  sendEvent('extension_error', {
    error_context: context,
    error_message: message.substring(0, 100)
  });
}

// Track performance
function trackTiming(category, duration) {
  sendEvent('performance_timing', {
    timing_category: category,
    timing_value: Math.round(duration)
  });
}

Reserved Event Names to Avoid

GA4 reserves certain event names. Using them causes silent failures:

Don’t UseUse Instead
errorextension_error
loginuser_login
searchuser_search
sharecontent_share
sign_upuser_sign_up

Debugging

Use the debug endpoint to validate events before production:

  1. Set DEBUG = true in your analytics module
  2. Send test events
  3. Check the response for validation messages
// Example debug response
{
  "validationMessages": [
    {
      "fieldPath": "events[0].params.session_id",
      "description": "Value must be a valid string.",
      "validationCode": "VALUE_INVALID"
    }
  ]
}

Also check GA4’s Realtime report to see events arrive within seconds.


Privacy Considerations

  1. Disclose analytics in your privacy policy — Required by Chrome Web Store policy
  2. Provide an opt-out mechanism:
async function isAnalyticsEnabled() {
  const { analyticsOptOut } = await chrome.storage.sync.get('analyticsOptOut');
  return !analyticsOptOut;
}

async function sendEvent(eventName, params = {}) {
  if (!(await isAnalyticsEnabled())) return;
  // ... rest of send logic
}
  1. Don’t track PII — Never send emails, URLs with user data, or personal identifiers
  2. Respect Do Not Track — Consider honoring the browser’s DNT header

What’s Next

GA4 Measurement Protocol gives you full analytics capabilities without violating MV3’s remote code restrictions. Track installations, feature usage, and errors to make data-driven decisions about your extension’s development.

For more ways to grow your extension, explore ExtensionBooster’s developer tools.

Share this article

Build better extensions with free tools

Icon generator, MV3 converter, review exporter, and more — no signup needed.

Related Articles