> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Google Analytics BigQuery Export

> Import analytics events from Google Analytics

export const Application = () => <Tooltip tip="An Algolia application is a self-contained environment with its own indices, configuration, and API keys. Applications don't share data or settings with each other.">
    application
  </Tooltip>;

export const AlgoliaSearch = () => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="20" height="20" className="inline" fill="none" role="presentation" ariaLabel="Algolia Search">
    <circle cx="40" cy="32" r="28" fill="#5468FF"></circle>
    <rect x="30" y="22" width="20" height="20" rx="10" fill="#fff"></rect>
    <path d="M43 63.5 54.5 60l6 17h-12L43 63.5Z" fill="#36395A"></path>
  </svg>;

If you're already using Google Analytics 4 (GA4) on your website,
you can capture [click and conversion events](/doc/guides/sending-events)
for Algolia's analytics and AI features with the BigQuery Export connector.

Google Analytics lets you export events to BigQuery from where Algolia's connector can import them.

## Set up Google Analytics BigQuery Export

Follow Google's [Set up BigQuery Export](https://support.google.com/analytics/answer/9823238) guide to link your GA4 property to BigQuery.

When configuring the export, choose the **streaming** option instead of **daily**.
This lets Algolia import events throughout the day instead of after a delay.

With the BigQuery export, Google syncs your GA4 data with BigQuery.
If your GA4 property is new, you need to collect some data first before you can see anything in BigQuery.

## Create a Google Cloud service account and key

Algolia's GA4 BigQuery Export connector needs permission to read from your BigQuery tables so that it can extract the data and import it.
To read your BigQuery tables,
the connector requires a Google service account and a key.

### Create a Google service account

1. Follow Google's guide: [Create service accounts](https://cloud.google.com/iam/docs/service-accounts-create).

   1. Enter a name and ID for the service account.
   2. Add the **BigQuery Job User** Identity and access management (IAM) role to this service account.
      For more information, see [BigQuery IAM roles and permissions](https://cloud.google.com/bigquery/docs/access-control).

2. In the [BigQuery console](https://console.cloud.google.com/bigquery), select the dataset with the BigQuery Export, open the **Actions** menu and select **Share > Manage Permissions.**

3. Click **Add Principal**. In the **New principals** text box, enter the name of the service account.

4. In the **Select a role** menu, select the **BigQuery > BigQuery Data Viewer** role.

### Create a key attached to the service account

1. In the Google Cloud console, go to [Service accounts](https://console.cloud.google.com/iam-admin/serviceaccounts).
2. Select your service account. Under **Actions**,  select **Manage keys**.
3. Select **Add key > Create new key**.
4. In the **Key type** menu, select **JSON**.
5. Click **Create**.

## Create GA4 BigQuery Export connection in Algolia

1. Go to the [Algolia dashboard](https://dashboard.algolia.com/explorer/browse) and select your Algolia <Application />.
2. On the left sidebar, select <Icon icon="database" /> **Data sources**.
3. On the [**Connectors**](https://dashboard.algolia.com/connectors/) page,
   select [**Google Analytics 4 BigQuery Export**](https://dashboard.algolia.com/connectors/google-analytics-4-bq-export/create) and click **Connect**.

### Configure your BigQuery source

The connector's source tells Algolia how to read from the BigQuery tables that Google Analytics syncs to.

1. In the **Google Service Account** box, select **Create a new Google Service Account authentication**.

2. In the **Create a new Google Service Account authentication** menu,
   upload a key file for a Google service account with these roles:
   **BigQuery Data Viewer** and **BigQuery Job User**.
   Enter a name for this account and click **Create authentication**.

3. In the **Enter your BigQuery table details** section,
   enter your GCP project ID and BigQuery dataset ID.

4. Select a table type:

   * **Streaming** if you configured your GA4 BigQuery Export for streaming exports (recommended).
   * **Daily**, if you configured your GA4 BigQuery Export to export daily,
     or if you use both streaming and daily exports.

   If both daily and streaming exports are active,
   the streaming tables expire at the end of each day,
   potentially before they're imported.

   <Note>
     GA4 BigQuery Export names tables based on your GA4 property's timezone,
     while Algolia uses UTC.
     To compensate for this,
     Algolia queries tables one day ahead of the current UTC date.
     For example, if today is June 1, 2025 in UTC,
     Algolia looks for data in the `events_20250602` or `events_intraday_20250602` table.
   </Note>

5. Enter a name for this data source.

6. Click **Save and continue**.

### Configure your destination

The connector's destination tells Algolia where to send source data.
For Google Analytics events data the destination is the Insights API.

1. Click **Create a new destination**.
2. Enter an index name for Algolia events imported by this connection.
3. To create Algolia credentials for the connector, click **Create one for me**.
4. Click **Save and continue**.

### Configure event mapping

The mapping section tells Algolia how to map [GA4 events](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm) to Algolia events.

Algolia only needs a subset of the events from GA4,
depending on the Algolia features you want to use.

The events mapping UI suggests mappings based on events found in your GA4 BigQuery Export that conform to the set of recommended [GA4 ecommerce events](https://developers.google.com/analytics/devguides/collection/ga4/ecommerce).
For other GA4 events you can define a custom mapping.

Most Algolia AI and analytics features rely on events being associated to a search with a [query ID](/doc/guides/sending-events/guides/queryid).
For more information about adding query IDs to your GA4 implementation,
see [Attribute events to searches](#attribute-events-to-searches).

Algolia only syncs events with a mapping.
Only create mappings for events that you don't already send to Algolia from other ingestion methods,
or they might be recorded twice.
For example, if you already send clicked items after searches events from the InstantSearch UI library,
don't map these in the connector.

For an overview of Algolia's event types, see [Event types](/doc/guides/sending-events/concepts/event-types).

To map click events that follow a search and include a `queryID`,
map a `position` field for each item in the GA4 event's `items` array.

If you omit the `position` field, Algolia uses the item's order in the array,
starting from 1.

#### Map conversion events

For ecommerce, map these conversion events:

| GA4                                                                                                                             | Algolia     |
| ------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| [`add_to_cart`](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#add_to_cart) | `addToCart` |
| [`purchase`](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#purchase)       | `purchase`  |

Other conversion events are optional.
For example, if you track additional conversion events, such as add to wish list,
you can map them to the generic Algolia `conversion` event.

#### Send GA4 user tokens in search requests

Google Analytics' persistent, anonymous user token is part of the imported GA4 events.
For analytics and Personalization, you need to send the same user token with your search requests.

To retrieve the GA user token, see [Google Analytics user token](/doc/guides/sending-events/concepts/usertoken#google-analytics).
Add it as [`userToken`](/doc/api-reference/api-parameters/userToken) parameter to your search requests.

<CodeGroup>
  ```cs C# theme={"system"}
  namespace Algolia;

  using System;
  using System.Collections.Generic;
  using System.Net.Http;
  using System.Text.Json;
  using Algolia.Search.Clients;
  using Algolia.Search.Http;
  using Algolia.Search.Models.Search;

  class SearchWithGAToken
  {
    private static string GetGoogleAnalyticsUserIdFromBrowserCookie(string cookieName)
    {
      return ""; // Implement your logic here
    }

    async Task Main(string[] args)
    {
      var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY"));
      var userToken = GetGoogleAnalyticsUserIdFromBrowserCookie("_ga");
      var searchParams = new SearchParams(
        new SearchParamsObject { Query = "SEARCH_QUERY", UserToken = userToken }
      );

      await client.SearchSingleIndexAsync<Hit>("INDEX_NAME", searchParams);

      string? loggedInUser = null;
      searchParams.AsSearchParamsObject().UserToken = loggedInUser ?? userToken;

      await client.SearchSingleIndexAsync<Hit>("INDEX_NAME", searchParams);
    }
  }

  ```

  ```dart Dart theme={"system"}
  import 'package:algolia_client_search/algolia_client_search.dart';

  String getGoogleAnalyticsUserIdFromBrowserCookie(String cookieName) {
    // This function is a placeholder for a real implementation
    return "";
  }

  void searchWithGAToken() async {
    final client =
        SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY');

    final userToken = getGoogleAnalyticsUserIdFromBrowserCookie("_ga");
    var searchParams =
        SearchParamsObject(query: "SEARCH_QUERY", userToken: userToken);

    await client.searchSingleIndex(
      indexName: "INDEX_NAME",
      searchParams: searchParams,
    );

    String? loggedInUser;
    searchParams = SearchParamsObject(
        query: "SEARCH_QUERY", userToken: loggedInUser ?? userToken);

    await client.searchSingleIndex(
      indexName: "INDEX_NAME",
      searchParams: searchParams,
    );
  }

  ```

  ```go Go theme={"system"}
  package main

  import (
  	"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
  )

  func getGoogleAnalyticsUserIDFromBrowserCookie(_ string) (string, error) {
  	return "", nil // Implement your logic here
  }

  func searchWithGAToken() {
  	client, err := search.NewClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
  	if err != nil {
  		// The client can fail to initialize if you pass an invalid parameter.
  		panic(err)
  	}

  	userToken, err := getGoogleAnalyticsUserIDFromBrowserCookie("_ga")
  	if err != nil {
  		panic(err)
  	}

  	searchParams := search.SearchParamsObjectAsSearchParams(
  		search.NewSearchParamsObject().
  			SetQuery("SEARCH_QUERY").
  			SetUserToken(userToken),
  	)

  	_, err = client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
  		"INDEX_NAME").WithSearchParams(searchParams))
  	if err != nil {
  		panic(err)
  	}

  	var loggedInUser *string

  	if loggedInUser != nil {
  		searchParams = search.SearchParamsObjectAsSearchParams(searchParams.SearchParamsObject.SetUserToken(*loggedInUser))
  	}

  	_, err = client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
  		"INDEX_NAME").WithSearchParams(searchParams))
  	if err != nil {
  		panic(err)
  	}
  }

  ```

  ```java Java theme={"system"}
  package com.algolia;

  import com.algolia.api.SearchClient;
  import com.algolia.config.*;
  import com.algolia.model.search.*;

  public class searchWithGAToken {

    private static String getGoogleAnalyticsUserIdFromBrowserCookie(String cookieName) {
      return ""; // Implement your logic here
    }

    public static void main(String[] args) throws Exception {
      try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
        String userToken = getGoogleAnalyticsUserIdFromBrowserCookie("_ga");
        SearchParamsObject searchParams = new SearchParamsObject().setQuery("SEARCH_QUERY").setUserToken(userToken);

        client.searchSingleIndex("INDEX_NAME", searchParams, Hit.class);

        String loggedInUser = null;
        searchParams.setUserToken(loggedInUser != null ? loggedInUser : userToken);

        client.searchSingleIndex("INDEX_NAME", searchParams, Hit.class);
      } catch (Exception e) {
        System.out.println("An error occurred: " + e.getMessage());
      }
    }
  }

  ```

  ```js JavaScript theme={"system"}
  import { algoliasearch } from 'algoliasearch';

  import type { SearchParams } from 'algoliasearch';

  const getGoogleAnalyticsUserIdFromBrowserCookie = (_: string) => {
    return ''; // Implement your logic here
  };

  const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');

  const userToken = getGoogleAnalyticsUserIdFromBrowserCookie('_ga');
  let searchParams: SearchParams = {
    query: 'SEARCH_QUERY',
    userToken,
  };

  await client.searchSingleIndex({ indexName: 'indexName', searchParams: searchParams });

  const loggedInUser: string | undefined = undefined;
  searchParams.userToken = loggedInUser ?? userToken;

  await client.searchSingleIndex({ indexName: 'indexName', searchParams: searchParams });

  ```

  ```kotlin Kotlin theme={"system"}
  import com.algolia.client.api.SearchClient
  import com.algolia.client.configuration.*
  import com.algolia.client.extensions.*
  import com.algolia.client.model.search.*
  import com.algolia.client.transport.*

  val getGoogleAnalyticsUserIdFromBrowserCookie: (String) -> String = {
    "" // Implement your logic here
  }

  suspend fun searchWithGAToken() {
    val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

    val userToken = getGoogleAnalyticsUserIdFromBrowserCookie("_ga")
    var searchParams = SearchParamsObject(query = "SEARCH_QUERY", userToken = userToken)

    client.searchSingleIndex(indexName = "INDEX_NAME", searchParams = searchParams)

    val loggedInUser: String? = null
    searchParams =
      SearchParamsObject(query = "SEARCH_QUERY", userToken = loggedInUser ?: userToken)

    client.searchSingleIndex(indexName = "INDEX_NAME", searchParams = searchParams)
  }

  ```

  ```php PHP theme={"system"}
  <?php

  require __DIR__.'/../vendor/autoload.php';
  use Algolia\AlgoliaSearch\Api\SearchClient;
  use Algolia\AlgoliaSearch\Model\Search\SearchParamsObject;

  $getGoogleAnalyticsUserIdFromBrowserCookie = function (string $cookieName): string {
      // Implement your logic here
      return '';
  };

  $client = SearchClient::create('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');

  $userToken = $getGoogleAnalyticsUserIdFromBrowserCookie('_ga');
  $searchParams = (new SearchParamsObject())
      ->setQuery('SEARCH_QUERY')
      ->setUserToken($userToken)
  ;

  $client->searchSingleIndex(
      'INDEX_NAME',
      $searchParams,
  );

  /** @var null|string $loggedInUser */
  $loggedInUser = null;

  $searchParams->setUserToken($loggedInUser ?? $userToken);

  $client->searchSingleIndex(
      'INDEX_NAME',
      $searchParams,
  );

  ```

  ```python Python theme={"system"}
  from algoliasearch.search.client import SearchClientSync


  def _get_google_analytics_user_id_from_browser_cookie(cookie_name: str) -> str:
      # Implement your logic here
      return ""


  try:
      _client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")

      user_token = _get_google_analytics_user_id_from_browser_cookie("_ga")
      search_params = {
          "query": "SEARCH_QUERY",
          "userToken": user_token,
      }

      _client.search_single_index(
          index_name="INDEX_NAME",
          search_params=search_params,
      )

      logged_in_user = None

      search_params["user_token"] = logged_in_user or user_token

      _client.search_single_index(
          index_name="INDEX_NAME",
          search_params=search_params,
      )
  except Exception as e:
      print(f"Error: {e}")

  ```

  ```ruby Ruby theme={"system"}
  require "algolia"

  def get_google_analytics_user_id_from_browser_cookie(_cookie_name)
    # Implement your logic here
    ""
  end

  client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")

  user_token = get_google_analytics_user_id_from_browser_cookie("_ga")
  search_params = Algolia::Search::SearchParamsObject.new(
    query: "SEARCH_QUERY",
    userToken: user_token
  )

  client.search_single_index("INDEX_NAME", search_params)

  logged_in_user = nil
  search_params.user_token = logged_in_user || user_token

  client.search_single_index("INDEX_NAME", search_params)

  ```

  ```scala Scala theme={"system"}
  import scala.concurrent.Future
  import scala.concurrent.ExecutionContext.Implicits.global
  import scala.concurrent.Await
  import scala.concurrent.duration.Duration

  import algoliasearch.api.SearchClient
  import algoliasearch.config.*
  import algoliasearch.extension.SearchClientExtensions
  import algoliasearch.search.SearchParamsObject

  val getGoogleAnalyticsUserIdFromBrowserCookie: String => String = _ => {
    "" // Implement your logic here
  }

  def searchWithGAToken(): Future[Unit] = {
    val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

    val userToken = getGoogleAnalyticsUserIdFromBrowserCookie("_ga")
    var searchParams = SearchParamsObject(query = Some("SEARCH_QUERY"), userToken = Some(userToken))

    Await.result(
      client
        .searchSingleIndex(
          indexName = "INDEX_NAME",
          searchParams = Some(searchParams)
        )
        .map(_ => Future.unit),
      Duration(5, "sec")
    )

    val loggedInUser = Some("...")
    searchParams =
      SearchParamsObject(query = Some("SEARCH_QUERY"), userToken = Some(loggedInUser.getOrElse(userToken)))

    Await.result(
      client
        .searchSingleIndex(
          indexName = "INDEX_NAME",
          searchParams = Some(searchParams)
        )
        .map(_ => Future.unit),
      Duration(5, "sec")
    )
  }

  ```

  ```swift Swift theme={"system"}
  import Foundation
  #if os(Linux) // For linux interop
      import FoundationNetworking
  #endif

  import AlgoliaCore
  import AlgoliaSearch

  let getGoogleAnalyticsUserIdFromBrowserCookie = { (_: String) in
      "" // Implement your logic here
  }

  func searchWithGAToken() async throws {
      let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY")

      let userToken = getGoogleAnalyticsUserIdFromBrowserCookie("_ga")
      var searchParams = SearchSearchParams.searchSearchParamsObject(
          SearchSearchParamsObject(query: "SEARCH_QUERY", userToken: userToken)
      )

      let _: SearchResponse<Hit> = try await client.searchSingleIndex(
          indexName: "INDEX_NAME",
          searchParams: searchParams
      )

      let loggedInUser: String? = "..."
      searchParams = SearchSearchParams.searchSearchParamsObject(
          SearchSearchParamsObject(query: "SEARCH_QUERY", userToken: loggedInUser ?? userToken)
      )

      let response: SearchResponse<Hit> = try await client.searchSingleIndex(
          indexName: "INDEX_NAME",
          searchParams: searchParams
      )
      print(response)
  }

  ```
</CodeGroup>

### Create your task

To start syncing GA4 events with Algolia, click **Create task**.
With a task type **streaming**, events will be regularly read from your BigQuery tables.
This isn't the same as Google Analytics' [**streaming**](#set-up-google-analytics-bigquery-export),
which refers to exporting events from GA4 to BigQuery.

<Note>
  The Google Analytics BigQuery connector doesn't backfill events.
  Once you create your connection, only new events written to BigQuery get synced.
  GA4 events written to BigQuery before the connection aren't synced.
</Note>

## Check your GA4 BigQuery Export connector in Algolia

After creating your connection,
use Algolia's **Connector Debugger** and **Events Debugger** to see the progress of the sync.

### Connector debugger

On the **Connectors** page,
open the [**Connector Debugger**](https://dashboard.algolia.com/connectors/debugger) tab to ensure that your events get imported from Google Analytics.

If your events aren't imported, the following section includes some troubleshooting steps.

#### Check for mapping errors

If you don't use the recommended GA4 fields,
you may need to edit your mapping.

For example, if your GA4 events don't use items, and you don't adjust your mapping accordingly,
you might receive an error similar to:

```txt theme={"system"}
Algolia API error [422] {"status":422,"message":"Event should specify either some ObjectIDs or some Filters"}
```

### Events debugger

The events debugger lets you check if the Insights API receives the events from the connector.

On the left sidebar, select
**Data sources** and
open the [**Debugger**](https://dashboard.algolia.com/events/debugger/) tab.

For more information, see [Validate your events](/doc/guides/sending-events/guides/validate).

## Attribute events to searches

Each Algolia search has a unique identifier: the query ID.
It's used to attribute click and conversion events to searches.
By default, GA4 doesn't track the query ID.
To link GA4 events to their originating search, [keep track of the current query ID](/doc/guides/sending-events/guides/queryid) and recording it as a custom event attribute in GA4, for example `algolia_query_id`.

```js JavaScript icon=code theme={"system"}
// This will be turned into a click event associated with a search.
gtag("event", "select_item", {
  // To avoid conflicts with GA4's query_id, use the algolia_query_id field to store Algolia's queryID.
  algolia_query_id: "tracked query id",
  items: [
    {
      item_id: "object1",
      item_name: "red shoes",
      position: 5,
    },
  ],
});

// For purchases, different items can be associated with different searches.
gtag("event", "purchase", {
  items: [
    {
      item_id: "object1",
      algolia_query_id: "object1 query",
    },
    {
      item_id: "object2",
      algolia_query_id: "object2 query",
    },
  ],
});
```
