This tutorial demonstrates how to track the usage of your extension using Google
Analytics. You can find a [working Google Analytics sample on GitHub](https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/functional-samples/tutorial.google-analytics),
where [`google-analytics.js`](https://github.com/GoogleChrome/chrome-extensions-samples/blob/main/functional-samples/tutorial.google-analytics/scripts/google-analytics.js) includes all the Google Analytics related code.

## Requirements

This tutorial assumes you are familiar with writing Chrome extensions. If you
need information on how to write an extension, read the [Getting Started
tutorial](https://developer.chrome.com/docs/extensions/mv3/getstarted).

You must also set up a [Google Analytics account](http://www.google.com/analytics) to track your extension.
Note that when setting up the account, you can use any value in the Website's
URL field, as your extension won't have a URL of its own.

## Use the Google Analytics Measurement Protocol

Since Manifest V3, Chrome Extensions are [not allowed to execute remote hosted
code](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview#remotely-hosted-code). This means you have to use the [Google Analytics Measurement
Protocol](https://developers.google.com/analytics/devguides/collection/protocol/ga4) for tracking extension events. The Measurement Protocol lets you
send events directly to Google Analytics servers with HTTP requests. A benefit
of this approach is that it lets you send analytics events from everywhere in
your extension, including your service worker.

> [!CAUTION]
> **Caution:** Using the Measurement Protocol means that some information, such as geolocation, won't be included.

### Set up API credentials

To send events to Google Analytics, you need an `api_secret` and a
`measurement_id`. Follow the [Measurement Protocol documentation](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag) to learn
more about general Measurement Protocol specifications.

#### Step 1: Create a Web data stream

Because Chrome Extensions are tracked as web environments, you must set up a
**Web data stream** in your Google Analytics property:

1. Go to the [Google Analytics Admin page](https://analytics.google.com/).
2. In the **Property** column, click **Data collection and modification** , then select **Data Streams**.
3. Click **Add stream** , then click **Web**.
4. Enter any placeholder URL in the **Website URL** field (for example, `https://extension` or your extension's Chrome Web Store URL).
5. Enter a **Stream name** (for example, `My Chrome Extension`).
6. Click **Create stream**.

Once created, your **Measurement ID** (which looks like `G-XXXXXXXXXX`) will be
displayed at the top of the Stream details page.

#### Step 2: Generate a Measurement Protocol API secret

To generate the `api_secret` required for the Measurement Protocol, navigate to
the settings for the Web data stream you just created:

1. Go to **Admin** \> **Data collection and modification** \> **Data Streams** and select your Web data stream.
2. In the **Events** section, click **Measurement Protocol API secrets**.

   > [!NOTE]
   > **Note:** If you don't see this option, ensure that your Google Analytics user account has \*\*Editor\*\* or \*\*Administrator\*\* permissions for the GA4 property. Standard viewer or analyst roles are not sufficient.

3. If prompted, read and accept the Measurement Protocol terms.

4. Click **Create**.

5. Enter a nickname for your secret (for example, `Chrome Extension Secret`)
   and click **Create** to generate the secret.

6. Copy the generated **Secret value**.

### Generate a `client_id`

The second step is to generate a unique identifier for a specific device/user,
the `client_id`. The ID should stay the same, as long as the extension is
installed on a user's browser. It can be an arbitrary string, but should be
unique to the client. Store the `client_id` in [`browser.storage.local`](https://developer.chrome.com/docs/extensions/reference/storage#storage-areas) to
make sure it stays the same as long as the extension is installed.

Using `browser.storage.local` requires the `storage` permission in your manifest
file:

manifest.json:

    {
      ...
      "permissions": ["storage"],
      ...
    }

Then you can use `browser.storage.local` to store the `client_id`:

    function getRandomId() {
      const digits = '123456789'.split('');
      let result = '';

      for (let i = 0; i < 10; i++) {
        result += digits[Math.floor(Math.random() * 9)];
      }

      return result;
    }

    async function getOrCreateClientId() {
      const result = await browser.storage.local.get('clientId');
      let clientId = result.clientId;
      if (!clientId) {
        // Generate a unique client ID, the actual value is not relevant. We use
        // the <number>.<number> format since this is typical for GA client IDs.
        const unixTimestampSeconds = Math.floor(new Date().getTime() / 1000);
        clientId = `${getRandomId()}.${unixTimestampSeconds}`;
        await browser.storage.local.set({clientId});
      }
      return clientId;
    }

### Send an analytics event

With the API credentials and the `client_id`, you can send an event to Google
Analytics using a [`fetch`](https://developer.mozilla.org/docs/Web/API/fetch) request:

    const GA_ENDPOINT = 'https://www.google-analytics.com/mp/collect';
    const MEASUREMENT_ID = `G-...`;
    const API_SECRET = `...`;

    fetch(
      `${GA_ENDPOINT}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`,
      {
        method: 'POST',
        body: JSON.stringify({
          client_id: await getOrCreateClientId(),
          events: [
            {
              name: 'button_clicked',
              params: {
                id: 'my-button',
              },
            },
          ],
        }),
      }
    );

This sends a `button_clicked` event which will appear in your [Google Analytics
events report](https://support.google.com/analytics/answer/9322688). If you want to see your events in the [Google Analytics
Realtime Report](https://support.google.com/analytics/answer/1638635), you need to provide two additional parameters:
`session_id` and `engagement_time_msec`.

### Use recommended parameters `session_id` and `engagement_time_msec`

Both `session_id` and `engagement_time_msec` are [recommended parameters when
using the Google Analytics Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports) as they are required for
user activity to display in standard reports like Realtime.

A `session_id` describes a period of time, during which a user continuously
interacts with your extension. By default, a session ends after 30 minutes of
user inactivity. There is no limit to how long a session can last.

In Chrome extensions, unlike in normal websites, there is no clear notion of a
user session. Hence, you must define what a user session means in your
extension. For example, every new user interaction might be a new session. In
that case, you can generate a new session ID with every event, such as using
a timestamp.

The following example demonstrates an approach that will timeout a new session
after 30 minutes of no events being reported (this time can be customized to
better suit your extension's user behavior). The example uses
[`browser.storage.session`](https://developer.chrome.com/docs/extensions/reference/storage#storage-areas) to store the active session while the browser is
running. Together with the session we store the last time an event was fired.
This way we can tell if the active session has expired:

    const SESSION_EXPIRATION_IN_MIN = 30;

    async function getOrCreateSessionId() {
      // Store session in memory storage
      let {sessionData} = await browser.storage.session.get('sessionData');
      // Check if session exists and is still valid
      const currentTimeInMs = Date.now();
      if (sessionData && sessionData.timestamp) {
        // Calculate how long ago the session was last updated
        const durationInMin = (currentTimeInMs - sessionData.timestamp) / 60000;
        // Check if last update lays past the session expiration threshold
        if (durationInMin > SESSION_EXPIRATION_IN_MIN) {
          // Delete old session id to start a new session
          sessionData = null;
        } else {
          // Update timestamp to keep session alive
          sessionData.timestamp = currentTimeInMs;
          await browser.storage.session.set({sessionData});
        }
      }
      if (!sessionData) {
        // Create and store a new session
        sessionData = {
          session_id: currentTimeInMs.toString(),
          timestamp: currentTimeInMs.toString(),
        };
        await browser.storage.session.set({sessionData});
      }
      return sessionData.session_id;
    }

The following example adds `session_id` and `engagement_time_msec` to the
previous button click event request. For `engagement_time_msec`, it is best to
provide the time elapsed since the last event. However, if this is not
feasible, you can provide a default value of `100 ms`.

    const GA_ENDPOINT = "https://www.google-analytics.com/mp/collect";
    const MEASUREMENT_ID = `G-...`;
    const API_SECRET = `...`;
    const DEFAULT_ENGAGEMENT_TIME_IN_MSEC = 100;

    fetch(
    `${GA_ENDPOINT}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`,
      {
        method: "POST",
        body: JSON.stringify({
          client_id: await getOrCreateClientId(),
          events: [
            {
              name: "button_clicked",
              params: {
                session_id: await getOrCreateSessionId(),
                engagement_time_msec: DEFAULT_ENGAGEMENT_TIME_IN_MSEC,
                id: "my-button",
              },
            },
          ],
        }),
      }
    );

The event will be displayed as follows in the Google Analytics Realtime report.

![Realtime events in Google Analytics.](https://developer.chrome.com/static/docs/extensions/how-to/integrate/google-analytics-4/image/realtime-events-google-a-111f521491126.png)

## Track page views in popup, side panel, and extension pages

The Google Analytics Measurement Protocol supports a special `page_view` event
for tracking page views. Use this to track users visiting your dialog boxes,
menu pages, side panels, and extension pages in a new tab. The `page_view` event
also requires the `page_title` and `page_location` parameters. The following
example fires a page view event at the document [`load`](https://developer.mozilla.org/docs/Web/API/Window/load_event) event for an
extension menu:

popup.js:

    window.addEventListener("load", async () => {
      fetch(`${GA_ENDPOINT}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`,
      {
        method: "POST",
        body: JSON.stringify({
          client_id: await getOrCreateClientId(),
          events: [
            {
              name: "page_view",
              params: {
                session_id: await getOrCreateSessionId(),
                engagement_time_msec: DEFAULT_ENGAGEMENT_TIME_IN_MSEC,
                page_title: document.title,
                page_location: document.location.href
              },
            },
          ],
        }),
      });
    });

The `popup.js` script must be imported in your popup's html file and should
run before any other script is executed:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Analytics Demo Popup</title>
        <script src="./popup.js" type="module"></script>
      </head>
      <body>
        <h1>Analytics Demo</h1>
      </body>
    </html>

The popup view will be displayed like any other page view in the Google
Analytics Realtime report:

![Page view event as it is displayed in the Google Analytics Realtime dashboard.](https://developer.chrome.com/static/docs/extensions/how-to/integrate/google-analytics-4/image/page-view-event-it-is-di-49a0970d10b4.png)

## Track analytics events in service workers

Using the Google Analytics Measurement Protocol makes it possible to track
analytics events in extension service workers. For example, by listening to the
[`unhandledrejection event`](https://developer.mozilla.org/docs/Web/API/Window/unhandledrejection_event) in your service worker, you can log any
uncaught exceptions in your service worker to Google Analytics, which can
greatly help to debug problems your users might report.

service-worker.js:

    addEventListener("unhandledrejection", async (event) => {
      fetch(`${GA_ENDPOINT}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`,
      {
        method: "POST",
        body: JSON.stringify({
          client_id: await getOrCreateClientId(),
          events: [
            {
              // Note: 'error' is a reserved event name and cannot be used
              // see https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#reserved_names
              name: "extension_error",
              params: {
                session_id: await getOrCreateSessionId(),
                engagement_time_msec: DEFAULT_ENGAGEMENT_TIME_IN_MSEC,
                message: event.reason.message,
                stack: event.reason.stack,
              },
            },
          ],
        }),
      });
    });

> [!CAUTION]
> **Caution:** Logging exceptions might accidentally leak personal information. Depending on your extension, it might be necessary to remove any information from the stack trace that shouldn't be sent to Google Analytics.

You can now see the error event in your Google Analytics reports:

![Error event as it is displayed in the Google Analytics events dashboard.](https://developer.chrome.com/static/docs/extensions/how-to/integrate/google-analytics-4/image/error-event-it-is-displa-67d188952536.png)

## Debugging

Google Analytics provides two helpful features for debugging analytics events
into your extension:

1. A [special debugging endpoint](https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=firebase) `https://www.google-analytics.com**/debug**/mp/collect` that will report any errors in your event definitions.
2. The [Google Analytics Realtime Report](https://support.google.com/analytics/answer/1638635) that will display events as they come in.