All Posts

Unlocking Insights with GA4 Measurement Protocol

February 3, 2024

In today's fast-paced digital analytics landscape, the key to staying ahead lies in leveraging versatile tools that enable both comprehensive data collection and its subsequent enhancement. Google Analytics 4 (GA4) Measurement Protocol stands out as a pivotal resource for marketers aiming to elevate their data-driven strategies. This tool excels in tracking crucial events server-side, including offline conversions, and enriching data collection with detailed insights. This guide delves into the nuances of GA4 Measurement Protocol, exploring its core, implementation techniques, and varied applications. ## Understanding GA4 Measurement Protocol Google Analytic 4 Measurement Protocol is a data collection method offering a flexible and scalable solution to track user interactions across digital assets such websites. This data collection method, unlike the traditional JavaScript-based tracking, allows for the collection of data through server-to-server communication. This not only provides enhanced accuracy but also facilitates data collection where traditional JavaScript tracking may be challenging. An example of this would be backend environments such as python, Ruby or PHP. It's essential to understand that this protocol is designed to augment measurements from gtag.js or Google Tag Manager, not to replace them. Clarifying this distinction is crucial, as it is often misconceived as a substitute for these tools. ## Setting Up: Measurement ID and API Secret ### Getting the Measurement ID and API Secret The first things we need to use the Measurement Protocol are the Google Analytics Measurement ID and the API Secret Key. The Measurement ID is rather easy to get. This a unique identifier for your GA4 property that gets generated with the creation of the property. To find this ID, you need to go to the admin section. Here are the steps to follow to get your Measurement ID: 1. Go to the Admin section 2. Under the **Data collection and modification**, click on Data streams 3. Click on the data stream of your choice 4. In the steam details section, you will find the Measurement ID 5. Copy this value As for the API secret key, it can be found within the same space as the Measurement ID. Once you have open the details of the data stream of your choice, under the Events section, click on **Measurement Protocol API secrets**. Next, click on create, give your key a nickname and click on save. ### Constructing the Payload Since we have the Measurement ID and the API secret, it is time to construct the payload we are going to send to Google Analytics 4. For this tutorial, we are going to use, gtag.js and not Firebase. Before we get into the weeds of this, please make sure to familiarize yourself with the following: - [How sessions are counted in Google Analytics 4](https://support.google.com/analytics/answer/9191807) - [Google Analytics 4 reserverd names](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#reserved_names) - [Google Tag API gtag function](https://developers.google.com/tag-platform/gtagjs/reference#get_mp_example) These concepts cover key components that you need to understand in order to use the Measurement Protocol. The rest of this tutorial can be read without reading the resources, but it is highly recommended that they are consulted and properly understood. Let's have a look at the payload of an event to be sent through the Measurement Protocol: ```js const measurement_id = `G-XXXXXXXXXX`; const api_secret = `<secret_value>`; fetch( `https://www.google-analytics.com/mp/collect?measurement_id=${measurement_id}&api_secret=${api_secret}`, { method: "POST", body: JSON.stringify({ client_id: "XXXXXXXXXX.YYYYYYYYYY", events: [ { name: "tutorial_begin", params: {}, }, ], }), }, ); ``` In order for an event to be sent through the Measurement Protocol, the following needs to be provided in the request body: - client_id: A unique identifier for a client. This is different than a Firebase app_instance_id. To get the client_id, use [gtag.js('get')](https://developers.google.com/gtagjs/reference/api#get_mp_example). - user_id: Optional. A unique identifier for a user. Can only contain utf-8 characters. See [User-ID for cross-platform analysis](https://support.google.com/analytics/answer/9213390) for more information about this identifier. It recommended that this parameter is sent in the request body. To get the user_id, you can use the \_ga cookie as described in [GA4 Cookie Usage](https://support.google.com/analytics/answer/11397207?hl=en). - consent: Optional. Learn how to set [consent settings](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference#payload_consent). - events: An array of event items. You can include multiple events in one request. In order for user activity to display in reports like [Realtime](https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-basics), engagement*time_msec and session_id must be supplied as part of the params for an event. The session_id can be fetched using the \_ga *<container-id> cookie. Let's look at a payload that provides all the parameters described above ```json { "client_id": "123456.7654321", "events": [ { "name": "campaign_details", "params": { "campaign_id": "google_1234", "campaign": "Summer_fun", "source": "google", "medium": "cpc", "term": "summer+travel", "content": "logolink", "session_id": "123", "engagement_time_msec": "100" } } ] } ``` ## Data Stitching4 As mentioned above, the Measurement Protocol is not to be used as a replacement for Google Tag Manager or gtag for data collection. It is a tool that allows to enhance tracking in order to enrich data collection. That being said, it is important to understand how data is captured, processed and stitched with the existing data. To do so, we can look at the documentation to understand how Google will process information we are sending through the Measurement Protocol. #### Stitching data to the current session One important distinction to make when working with the Measurement Protocol is the environment we are in when data is being sent. Usually, said environments are back-end environments where browser-level data is not accessible. Why is this important? Data stitching. Data stitching is the practice of tying data collected from different environments to give a clearer view over a specific topic. In our case, we will be tying data sent from our back-end environments to browser-level data to give a clearer picture about user behaviour. One common case of stitching data is **offline conversions**. For instance, we can use the Measurement Protocol to track users down a funnel as they move from lead, to SQL to SAL etc. However, in order for the data to be used outside of Events reporting, we will need an indicator that allows for such a thing to happen, and that indicator is the session_id. The session_id is a key parameter in data stitching and it is highly recommended that it is send along in the event payload. Let's breakdown why such an event parameter is important. As highlighted by Google's documentation for the Measurement Protocol, when a new session_id is sent in the payload a new session will start, hence a new session will be counted. Why does Google count a new session? It is because sessions count is an estimation of the unique collected session ids. Before working with server-side events, it is highly recommended to familiarize yourself with how [sessions are counted](https://support.google.com/analytics/answer/9191807), and how your implementation can influence this metric. Let's take a look at how the payload would look like with the session_id: ```json { "client_id": "123456.7654321", "events": [ { "name": "campaign_details", "params": { "campaign_id": "google_1234", "campaign": "Summer_fun", "source": "google", "medium": "cpc", "term": "summer+travel", "content": "logolink", "session_id": "123", "engagement_time_msec": "100" } } ] } ``` To reiterate, if the session_id does not exist a new session will start. If the session_id does exist, that's when the magic of data stitching happens. When Google is able to identify the session, the server-side events get stitched to the session and attribution becomes possible. This is very important especially if the server-side event will be used as a conversion. ### Stitching data to the current user While session-level attribution is important, being able to stitch data to the user is beneficial. For instance, such practice allows us to breakdown server-side events based on first touch attribution, that is the first channel of acquisition. Combining both user and session stitching gives full visibility as we can combine both browser-level data and server-side data to understand the user journey. Stitching data to the user happens when we send the client_id \in the payload. The user_id can also be sent in the event payload if you have it enabled for your site. The most important parameter, though, is the client_id as Google uses it to identify users. If you are sending the user_id in the payload, make sure that you are sending it at the browser-level as well to avoid duplication. Google Analytics 4 will not depulicate users if the client_id or the user_id changes. If you want to use the user_id in the Measurement Protocol , it is crucial to ensure that you are identifying users prior to sending the server-side event. Here's what the payload would look like with the user_id: ```json { "client_id": "123456.7654321", "events": [ { "name": "campaign_details", "params": { "campaign_id": "google_1234", "campaign": "Summer_fun", "source": "google", "medium": "cpc", "term": "summer+travel", "content": "logolink", "session_id": "123", "user_id": 542197212, "engagement_time_msec": "100" } } ] } ``` ## Events Validation Before sending data to Google Analytics 4, you can validate your events to avoid any errors. Google Analytics 4 has a validation server that you can send your events to for debugging purposes. The only difference between the Measurement Protocol and the Validation Server is the URL /debug/mp/collect. Everything else remains the same. ```js const measurement_id = `G-XXXXXXXXXX`; const api_secret = `<secret_value>`; fetch( `https://www.google-analytics.com/debug/mp/collect?measurement_id=${measurement_id}&api_secret=${api_secret}`, { method: "POST", body: JSON.stringify({ client_id: "XXXXXXXXXX.YYYYYYYYYY", events: [ { name: "tutorial_begin", params: {}, }, ], }), }, ); ``` ## Response | Key | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | | `validationMessages` | Array<[ValidationMessage](https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=gtag#validation_message)> | An array of validation messages. | ## ValidationMessage | Key | Type | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `fieldPath` | string | The path to the field that was invalid. | | `description` | string | A description of the error. | | `validationCode` | [ValidationCode](https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=gtag#validation_code) | A ValidationCode that corresponds to the error. | ## ValidationCode | Value | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VALUE_INVALID` | The value provided for a `fieldPath` was invalid. See [limitations](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events#limitations). | | `VALUE_REQUIRED` | A required value for a `fieldPath` was not provided. | | `NAME_INVALID` | The name provided was invalid. See [limitations](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events#limitations). | | `NAME_RESERVED` | The name provided was one of the reserved names. See [reserved names](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference#reserved_names). | | `VALUE_OUT_OF_BOUNDS` | The value provided was too large. See [limitations](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events#limitations). | | `EXCEEDED_MAX_ENTITIES` | There were too many parameters in the request. See [limitations](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events#limitations). | | `NAME_DUPLICATED` | The same name was provided more than once in the request. | ## Real-World Application and Testing To test all of this, we are going to use Postman to send the Post Request to Google Analytics and see if our data is going to be captured and validate the parameters. Also, we are going to send 2 events in this payload instead of one. Here's the body of the payload: ```json { "client_id": "1706397545.10.1.1706397554.51.0.0", "events": [ { "name": "viewed_collection_page", "params": { "campaign_id": "google_1234", "campaign": "Summer_fun", "source": "google", "medium": "cpc", "term": "summer+travel", "content": "logolink", "session_id": "123", "user_id": "542197212", "engagement_time_msec": "100" } }, { "name": "viewed_store_page", "params": { "campaign_id": "google_1234", "campaign": "Summer_fun", "source": "google", "medium": "cpc", "term": "summer+travel", "content": "logolink", "session_id": "123", "user_id": "542197212", "engagement_time_msec": "100" } } ] } ``` And this is the response we get with this payload **Status: 204 No Content**. This status code means that the server successfully processed the request but is returning no content. And, this is what we see in Google Analytics 4's Real Time reports: ![alt text](./images/ga4-real-time-view-mp.png) Notice that both events have been processed and only one user has been detected. This is because only one client_id has been sent. Even if we changed the user_id, Google Analytics 4 will still show one user. However, it is not recommended to change the user_ids at random. ## Conclusion The GA4 Measurement Protocol serves as a cornerstone for marketers seeking to advance their analytics capabilities. Through server-side tracking and data augmentation, it offers unparalleled insights into user behavior, making it an indispensable tool in the marketer's arsenal. This guide aims to demystify the protocol and encourage its adoption for more sophisticated data analysis strategies.

Data Visualization with Open Source Web Analytics

February 24, 2024

In our [previous discussion](/swetrix), we explored Swetrix, an innovative, open-source web analytics tool beneficial for businesses seeking to evaluate their online performance. This chapter` will guide you on leveraging Swetrix data to create a personalized dashboard, providing essential insights for website optimization. For those interested, the complete code is accessible on [Github](https://github.com/AzizDhaouadi/swetrix-plotly), allowing you to clone the project and tailor your dashboard. This tutorial employs Express, JavaScript, and Plotly.js to craft a dashboard showcasing data such as pageviews, unique visitors, session durations, bounce rates, average page speeds, and more, offering a comprehensive view of your website's performance. The flexibility of the project means you can adjust the dashboard's design to meet your specific needs. For this tutorial, we will be using Express, JavaScript and Plotly.js to build a dashboard that shows the following data: - Pageviews - Unique visitors - Session duration - Bounce rate - Average page speed - Traffic per country - Unique visitors Tech Breakdown - Unique visitors per browser - Unique visitors per Device Category - Unique Visitors per OS - Visits per page - Visits per referrer - Custom events breakdown As you can see this dashboard contains all the necessary information for us to make decisions about the performance of our website. And the good news is we can make this dashboard look and feel the way we want as we have full control over the code. ## Project Anatomy The first thing we need to get started is a Swetrix account with data collected in order to visualize it. If you need to create a Swetrix account, you can so using their [sign up page](https://swetrix.com/signup). If it's your first time using this tool, you can refer to their [official documentation](https://docs.swetrix.com/). Once you are setup, it's time to get started. ### Express, Plotly.js, and Dependencies As mentioned above, the main components of this project are Express, Plotly.js and JavaScript. If you do note have Node.js installed on your machine, please do so. You will this in the upcoming steps. The following are the dependencies we need to get things going: - Express - Node-fetch - Dotenv You do not have to worry about installing these if you are going to clone the project. The Readme file contains all the details you need to get started. The point of explaining these dependencies is to understand their role in the project, so everything is clear. ## Swetrix's Statistics API A core aspect of this project is the Swetrix Statistics API, which provides access to the data displayed in the Swetrix dashboard. We will use specific API endpoints (/log, /log/birdseye, /log/performance) to fetch necessary data for our dashboard. Familiarity with the Fetch API and asynchronous JavaScript will be beneficial here.. If you are not familiar with the fetch API, you can read more about it in depth in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). Additionally, being familiar with Async JavaScript would be a plus. ## API Endpoints To retrieve the data we need, we will be using 3 endpoints being: - /log - /log/birdseye - /log/performance Each endpoint will return to us a specific set of data that we will use to create our charts. ### Log Endpoint This endpoint will return all the data we need about our traffic. In truth, most of the charts mentioned above will be built using the data from this endpoint. ### Log Birdseye Endpoint This endpoint will return the traffic overview data. You can this of it as an executive summary of the log endpoint data set. ### Log Performance API This endpoint returns data regarding the performance of our website. For instance, the average page load speed time is one of the data points that can be found querying this endpoint. ## Implementation Details ### Index.js File Now that we understand what our data source is and what our dependencies are, it's time to dive into the source code. The first file we are going to take a look at is the index.js file. The latter is the point of entry to our application and contains the code to tell our server what to do. The code in this file is setting essential variables we need for our application, and defines API routes that fetch data from the Swetrix Statistics API when queried. Let's take a look at the fetch variable. ```js const fetch = (...args) => import("node-fetch").then(({ default: fetch }) => fetch(...args)); ``` One of the project's dependencies is Node-fetch. This is a lightweight module that brings the Fetch API to Node.js. You can read more about this package on [NPM's documentation](https://www.npmjs.com/package/node-fetch). #### app.get('fetch/\*') The index.js file contains multiple app.get functions with routes that look like fetch/\*. These functions tell our server what to do when a get request at the given route is called. For instance, when the server gets a get request at the route /fetch/trafficData it will send a get request to Swetrix's log API endpoint to fetch traffic data. Each app.get function is designed to get specific data from Swetrix's servers. The reason we are defining these functions instead of using the fetch API client-side is to avoid exposing the API key. In fact, client-side fetch requests will be sent to our designated endpoints which in turn will fetch the required data. The dotenv package helps use define and use environment variables in our project so that sensitive information like API keys are not exposed. If you are going to use environment variables ensure to add the .env file to the add .gitignore file so that you do not commit your data to a repository. #### app.use() In our Express application, using `app.use(express.static(path.join(__dirname, "/public")));` is a good way to serve static files such as images, CSS files, and JavaScript files. This line tells Express to serve the static files from a directory named public located in the root of your application. The path.join(\_\_dirname, "/public") part constructs an absolute path to the public directory, which helps avoid issues related to the difference in file paths across operating systems. ## Index.html The index.html file has a very straightforward structure. Note that we are using Boostrap for this project to make styling and responsiveness much easier to handle. Most of the HTML elements in this file will have their content added/updated with JavaScript. ## JS folder In our JS folder, you will see two subfolders and 3 files. Each file starts with render and the rest of the name describes what it will be rendering. For instance, the file `renderTrafficData` is going to be used to render traffic data. Let's breakdown the anatomy of the file in order to get an idea of how data is being rendered. ### renderTrafficData.js The file contains 7 import statements. The import statements are getting us the functions necessary to render the charts using Plotly.js. The next thing we see in the file is the`fetchTrafficData` which will be used to fetch the data we are going to pass to our visualization functions. The fetch request is actually sent to our endpoints. The second function uses the data we fetched to visualize the data using Plotly. All the Plotly-related code is in the dashboardVisualizations folder. The code was kept separate to make readability easier. ## Conclusion This tutorial provides a comprehensive guide to building a custom dashboard using Swetrix data. By understanding the project's structure and implementation, you can adapt and extend your dashboard, integrating web analytics with other tools for richer insights.

How to Configure Data Redaction in Google Analytics 4

October 3, 2024

Data redaction is a feature in Google Analytics 4 that helps you protect your property ensuring, to an extent, that no Personally Identifiable Information (PII) is collected. Configuring this feature in Google Analytics 4 is essential for adding an extra layer of security to your data collection efforts. ### What is a URL query parameter? A URL query parameter is a key-value pair that follows the question mark (?) or (&) at the URL. For example, the event parameter, **form_destination** might contain a URL with query parameters such as `https://www.example.com/us?language=english&catalog=summer24` ## How does data redaction work? This feature uses text patterns to identify likely to be email addresses in all event parameters and URL query parameters that are included as part of the event parameters `page_location`, `page_referrer`, `page_path`, `link_url`, `video_url`, `form_destination` Data redaction evaluates events before they are collected to find and redact (remove) any text it understands as an email or query parameter key-value pair. After the redaction of the text, data collection proceeds as expected. Data redaction evaluates events before they are collected to find and redact (remove) any text it understands as an email or query parameter key-value pair. After the redaction of the text, data collection proceeds as expected. Again, while this is a powerful tool preventing you from sending or collecting PII inadvertently, the ultimate responsibility falls on you, the entity collecting the data. Please ensure that you are meeting all regulatory requirements for data collection, and do not rely on data redaction as your primary defence against sending or collecting PII. As mentioned before, this feature should be another tool in your arsenal and not your main tool. ### Is data redaction activated by default for Google Analytics 4? Any property created after the release of this feature will have data redaction for email on by default. Data redaction for URL query parameters will still have to be configured separately. If your property was created before the release of this feature, you will need to configure data redaction for both email addresses and URL query parameters. <aside> 💡 **Please note that data redaction is only available for web data streams.** </aside> ### Data Redaction - Good to Know - Data redaction evaluates event data for email addresses on a best-effort basis - Data redaction occurs client side after Analytics modifies or creates events and **before** data is sent to Analytics - Data redaction accepts recent-encoded URL query parameters, including Unicode characters accepted by browsers - Data redaction may incorrectly interpret text as as an email address and redact the text. For instance, if the text contains `@` followed by a top-level domain name it may be incorrectly removed - Data redaction does not evaluate HTTP-header value - Data redaction won’t prevent the collection of PII via Measurement Protocol or Data Import ## How to configure data redaction in Google Analytics 4? To configure data redaction in Google Analytics 4, you can follow these steps: 1. In the Admin section, under *Data collection and modification*, click **Data Streams** 2. Select the relevant web data steam 3. Click **Redact data** 4. Turn on the switch for each option you want to activate data redaction for 1. Email 2. URL query parameters 5. If you chose to active data redaction for URL query parameters, enter a comma delimited list 1. For example: first_name, last_name, email, address, phone_number 6. The last step is to test URL query parameters data redaction using the **Test data redaction** feature to see how Analytics removes the data in question 1. Under the data in the text field. Make sure it contains URL query parameters you entered in the previous step 1. https://www.example.com/?firstname=John&lastname=Doe&email_address=johandoe@example.com 2. Click preview redacted data 1. [https://www.example.com/?firstname=(redacted)&lastname=(redacted)&email_address=(redacted)](https://www.example.com/?firstname=(redacted)&lastname=(redacted)&email_address=(redacted)) If you are satisfied with the result, then you are done. Otherwise, change the URL query parameters until you get the intended result. ## Conclusion Data redaction is a very powerful feature that allows you to protect your property from collecting PII inadvertently. Combining this with Consent Mode can ensure that your business is compliant with data regulations and that no data is being passed while it should not.

Tracking Hubspot Form Submissions with Hubspot's Global Events and Google Tag Manager

April 30, 2024

HubSpot is an indispensable tool for marketing teams, renowned for its versatility in executing diverse marketing initiatives ranging from campaign management to the creation of landing pages. One of the platform's pivotal features is its form-building capability that enables the capture of essential lead data on marketing websites. Mastery of tracking form submissions is crucial, as it plays a significant role in understanding and enhancing user interactions and behavior analytics. This guide delves into how to effectively use HubSpot's global form events to monitor these submissions and integrate them with Google Analytics. By doing so, you can refine your strategies and achieve a deeper insight into customer engagement, thereby driving improved outcomes for campaigns. ## Hubspot Global Form Events Hubspot forms emit global events when submitted which can be used to trigger custom JavaScript. Before getting into the tracking code, it is important to note: - These global events are non-blocking so they cannot be used to prevent a form submission - These events cannot be used to change form submission data ## OnBeforeFormInit This event is called before the Hubspot form has been inserted into the DOM. This is what the event will output: ```json { "type": "hsFormCallback", "eventName": "onBeforeFormInit", "id": "Form submitted ID", "data": {} } ``` ## OnFormReady This event is called when the form has been inserted into the DOM. You can use this event to build a funnel that starts with the funnel loading. The following code snippet will log an event into the dataLayer indicating that the form was loaded: ```js window.addEventListener("message", function (event) { if ( event.data.type === "hsFormCallback" && event.data.eventName === "onFormReady" ) { window.dataLayer.push({ event: "form_loaded", form_id: event.data.id, }); } }); ``` ## onBeforeFormSubmit This is event is called at the start of the form submission but before the submission has been persisted. This event returns an array that contains the form data. This code snippet will log an event into the dataLayer indicating that the form has been submitted ```js window.addEventListener("message", function (event) { if ( event.data.type === "hsFormCallback" && event.data.eventName === "onBeforeFormSubmit" ) { window.dataLayer.push({ event: "form_submitted", form_id: event.data.id, }); } }); ``` ## onFormSubmitted This event is called when the form has been submitted and the submission has been persisted. This is the event that you want to use in order to track successful form submissions. ```js window.addEventListener("message", function (event) { if ( event.data.type === "hsFormCallback" && event.data.eventName === "onFormSubmitted" ) { window.dataLayer.push({ event: "form_submitted", form_id: event.data.id, }); } }); ``` ## Next steps After logging your event of choice into the dataLayer, the next step is to create a Google Analytics: GA4 Event tag. In the `Measurement ID`, enter your property's Measurement ID. In the Event Name field, event {{Event}}. This refers to the custom Event variable in Google Tag Manager. Before using the variable, make sure it is defined in workspace. In the Event Parameter section, click on the Add parameter button. In the Event Parameter input field, type in `form_id` and in the value enter `{{DLV - form_id}`. This refers to a custom dataLayer variable with the name DLV - form_id so make sure it is defined before using it. ## Conclusion Using the above mentioned Global events emitted from the Hubspot forms, you can track successful form submissions and send data to Google Analytics 4 or even Google Ads or any other analytics library.

Set up Enhanced Conversions for Google Ads with Google Tag Manager

September 15, 2024

As tracking restrictions become tighter and tighter, enabling ad platforms with First-Party data has never been more important. In this tutorial, we will discuss how you can implement Google Ads' Enhanced Conversions with Google Tag Manager to provide Google Ads with the right data for your campaign optimizations. ## What is Enhanced Conversions? Enhanced conversions is a feature that aims at improving the accuracy of conversion measurement. The feature supplements any existing conversion tags by sending hashed first-party data collected from the website while being privacy compliant. Enhanced Conversions uses a secure one-way hashing algorithm known as `SHA256` on your data before sending to Google. This ensures that customer data such as email addresses are protected. This hashed data is later matched with signed-in Google accounts to attribute your conversion to ad events, such as clicks or views. <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> 💡 Conversions measured by importing <a href="https://support.google.com/google-ads/answer/2401634" target="_blank">Google Analytics</a> goals aren't supported for enhanced conversions. If you'd like to use enhanced conversions, consider setting up a new Google Ads conversion action with the Google tag or Google Tag Manager. </aside> ## Implementation Pre-requisites Before we dive into the implementation, it is recommended to review this list to ensure that the implementation goes as smoothly as possible: - Know where the conversion tag fires. What is the conversion page URL? This can be a confirmation page URL. - What is the conversion event trigger? How does the conversion tag fire? Is it a button click? Or is it a form submission? Or is it on a page view? - Make sure that there is first-party customer data available on the conversion page. The available data should be email, full name and home address and/or phone number - How is the conversion tracking set up? This setup requires an understanding of the conversion tracking setup and possibly code changes. So, make sure you have access to the code base or you have access to your developers to communicate requirements - If your conversion tags are set using URLs, enhanced conversions can only be set using Javascript or CSS selectors options or automatic enhanced conversions - The impact results in the conversion action table willƒ be seen about 30 days after implementing enhanced conversions successfully. ## Turning on Enhanced Conversions for Web <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> 🚨 The following instructions are based on the new design of the Google Ads user experience. </aside> 1. In your Google Ads account, click the **Goals** icon . 2. Click the **Conversions** drop down in the section menu. 3. Click **Settings**. 4. Expand the “Enhanced conversions” section. 5. Check “Turn on enhanced conversions for web”. 6. Review compliance statement. To enable enhanced conversions you must confirm that you'll comply with our [policies](https://support.google.com/adspolicy/answer/7475709) and that the [Google Ads Data Processing Terms](https://privacy.google.com/businesses/processorterms/) apply to your use of enhanced conversions. Click **Agree** **and continue** to acknowledge your acceptance of these terms. 7. Click the dropdown to choose a method for setting up and managing user-provided data. Select “Google Tag Manager”. <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> 🚨 <b>Note</b>: If Google enhanced conversions data is collected in a way that’s different from Google Tag Manager, that data may not be processed. For instance, since we selected that we are going to send enhanced conversion data through Google Tag Manager and we try sending data through the Google Ads API, the data sent through API won’t be processed. </aside> 8. Click Go to **Google Tag Manager** and follow the instructions to complete the process in Google Tag Manager ## Setting up Enhanced Conversions in Google Tag Manager There are 3 ways we can set up enhanced conversion in Google Tag Manager, and we will review them separately so you can choose the method that suits you best. - **Automatic collection**: This method allows the tag to automatically detect and collect user-provided data on the page. This method is the easiest and quickest out of the three, but it is not as reliable as adding a code snippet or specifying CSS selectors or Javascript variables. - **Code**: Add a code snippet on the website that sends hashed user-provided data for matching. This method is the most reliable as it ensures that you are consistently sending properly formatted data whenever the conversion tag fires. This method is the most suitable if you wish to enhance the accuracy of enhanced conversions. - **Manual configuration**: Specify which CSS selectors or Javascript variable on the conversion page contain the relevant user-provided data. This method is more precise than the automatic collection, but it is not as reliable or consistent as adding code to the website. This method is not recommended is you frequently change the website’s code. The latter can create inconsistencies that may break enhanced conversion tracking. If you do consistently update your code base, it is recommended that you use dataLayer variable or add code to your website to capture user-provided data. To reiterate, if you frequently change the website formatting, this method is not recommended it could disrupt the CSS selector method event the Javascript method if you are creating your variables based on `querySelector` or `querySelectorAll` <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> 💡 It is best practice to use the ID or data attributes to retrieve the value from a DOM element. IDs are unique and less likely to change over time. Data attributes are also less prone to change over time. Both of these attributes change less over time unlike properties such classes. </aside> ### Set up enhanced conversions using automatic collection in Google Tag Manager Automatic collection can be set up using 2 different methods: - **Standard automatic enhanced conversions**: this is the recommended method when user-provided data is available on the conversion event page. For instance, if your conversion event page is a purchase confirmation page and data present on the page includes the user details such as email, phone number or address, use this method. - **Automatic enhanced conversions with the user-provided data event tag**: this is the recommended method when the customer data is not available on the conversion event page, but it is instead available on a previous page. For instance, if the conversion event page is the purchase confirmation page and user’s data is entered on a previous page prior to the confirmation page, use this method. <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> <b>Note</b>: If you use the user-provided data event tag method, first-party customer data will be automatically detected on pages that users visit before reaching the conversion page. With this setup, you authorize Google to use an ads cookie to collect hashed, first-party customer data on your behalf and to connect that data with subsequent conversion events occurring within the same user session on your behalf. All data not connected to a conversion is deleted. If you implement Consent Mode, the ads cookie will be subjected to the ad_storage consent status of the Consent Mode feature, where implemented. </aside> ### Set up standard automatic enhanced conversions 1. Click **Workspace**, then click **Tags** from the navigation menu. 2. Select the Google Ads conversion tracking tag that you’d like to implement enhanced conversions with and edit that tag. - Make sure that this conversion action has the same conversion tracking ID and label as the conversion action that you enabled enhanced conversions for in your Google Ads account. 3. Click **Include user-provided data from your website**. 4. In the dropdown, select **New Variable** or use an existing variable if you’ve already set one up. 5. Select **Automatic collection**. 6. Click **Save** for the variable and then save the conversion tracking tag. ### Set up automatic enhanced conversions with user-provided data event tag 1. Click **Workspace**, then click **Tags** from the navigation menu. 2. Click **New** to create a new tag. 3. Click **Tag Configuration** and select **Google Ads User-Provided Data Event**. 4. Fill in your Google Ads Conversion Tracking ID. - Make sure that this conversion action has the same conversion tracking ID and label as the conversion action that you enabled enhanced conversions for in your Google Ads account. 5. In the dropdown, select **New Variable**. 6. Select **Automatic**. 7. Name the variable. 8. Click **Save**. 9. Click **Triggering** in the Google Ads User-Provided Data Event Tag. This is where you anticipate that the user data will be available. 10. Click the plus icon. 11. Click **Trigger Configuration**. 12. Click **Form Submission**. 13. Select **All Forms**. 14. Select **Save** and then save your new Google Ads User-Provided Data Event tag. <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> <b>Note</b>: You must select form submission for enhanced conversions to work properly </aside> ### Set up conversions using manual configuration in Google Tag Manager The first step in this method is to find which variables we are going to use to set up enhanced conversions. 1. Navigate to the page where user-provided data appears. This data may appear on the conversion page or on a previous page. Identify any customer data that is displayed on the page you want to send to Google. At least one of the following fields must be provided: 1. Email (preferred) 2. Address - First name, last name, postal code, and country are required 1. Optional: Street address, city, and region as additional match keys 3. A phone number can also be provided as a standalone match key but is recommended to be sent along with an email 2. Once the customer data on the page is identified, you will need to copy the CSS selectors and enter those in Google Tag Manager. You can also data layer variables if they already exist. This allows enhanced conversion tags to know which pieces of data to hash and send to Google. It’s important to keep the conversion page open. 3. In a separate tab, open Google Tag Manager to set up manual enhanced conversions. This can be done in 2 different ways. 1. **Standard manual enhanced conversions**: Use this method when user-provided data is available on the conversion event page. 2. **Manual enhanced conversions with user-provided data event tag**: Use this method when user-provided data is not available on the conversion event page but is instead available on a previous page <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> <b>Note</b>: If you use the user-provided data event tag method, first-party customer data will be automatically detected on pages that users visit before reaching the conversion page. With this setup, you authorize Google to use an ads cookie to collect hashed, first-party customer data on your behalf and to connect that data with subsequent conversion events occurring within the same user session on your behalf. All data that is not connected to a conversion is deleted. If you implement Consent Mode, the ads cookie will be subjected to the ad_storage consent status of the Consent Mode feature, where implemented. </aside> ### Set up standard manual enhanced conversions 1. Click **Workspace**, then click **Tags** from the navigation menu. 2. Select the Google Ads conversion tracking tag that you’d like to implement enhanced conversions with and edit that tag. - Make sure that this conversion action has the same conversion tracking ID and label as the conversion action that you enabled enhanced conversions for in your Google Ads account. 3. Click **Include user-provided data from your website**. 4. In the dropdown, select **New Variable** or use an existing variable if you’ve already set one up. 5. Select **Manual configuration**. - You may also select “Code” if you'd like to use Custom Javascript or other data objects to send your data through Google Tag Manager. This method requires data to be formatted in a particular way, so if you’d like to do this, you can read the “Code” instructions below. Here you'll see “User provided data” at the top of the page, followed by all the pieces of customer data which you can include as part of your enhanced conversion tag. 6. For the relevant user data field that you'd like to provide via enhanced conversions, click on the dropdown menu and select **New Variable**. <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> <b>Note</b>: if you already have unhashed variables in the data layer, you can select those instead of creating new variable. </aside> 1. In the “Variable Configuration” screen, select **Choose a variable type to begin setup**. In the “Choose Variable Type” screen, select **DOM Element**. 2. Back on the “Variable Configuration” screen, change “Selection Method” in the dropdown to “CSS Selector.” 3. Give your variable a title. 4. Enter the CSS selector that references your users’ data into the “Element selector” input field (see section below on how to find the CSS Selector). You can leave the “Attribute name” field blank (more on this below). 5. Click **Save** for the variable and then save the conversion tracking tag. ### Set up manual enhanced conversions with the user-provided data event tag 1. Click **New** to create a new tag. 2. Click **Tag Configuration** and select **Google Ads User-Provided Data Event**. 3. Fill in your Google Ads Conversion Tracking ID. - Make sure that this conversion action has the same conversion tracking ID and label as the conversion action that you enabled enhanced conversions for in your Google Ads account. 4. In the dropdown, select **New Variable**. 5. Select **Manual configuration**. 6. For the relevant user data field that you'd like to provide via enhanced conversions, click on the dropdown menu and select **New Variable**. 7. In the “Variable Configuration” screen, select **Choose a variable type to begin setup**. In the “Choose Variable Type” screen, select **DOM Element**. 8. Back on the “Variable Configuration” screen, change “Selection Method” in the dropdown to “CSS Selector.” 9. Give your variable a title. 10. Enter the CSS selector that references your users’ data into the “Element selector” input field (see section below on how to find the CSS Selector). You can leave the “Attribute name” field blank. (more on this below). 11. Click **Save**. 12. Click **Triggering** in the Google Ads User-Provided Data Event Tag. This is where you anticipate that the user data will be available. 13. Click the plus icon. 14. Click **Trigger Configuration**. 15. Click **Form Submission**. 16. Select **All Forms** 17. Select **Save** and then save your new Google Ads User-Provided Data Event tag <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> <b>Note</b>: You must select form submission for enhanced conversions to work properly </aside> ### Identifying CSS Selectors and inputing them in Google Tag Manager This section is for readers that are not familiar with CSS Selectors. If you are, you can skip ahead. This section will show you how to copy CSS Selectors from you conversion event page and paste them into enhanced conversions variables. If you already have data layer variables that capture the relevant data, you can use those instead of creating new variables. 1. Navigate to your website in a separate and keep (or open) a Google Tag Manager page 2. Identify the user-provided data you want to send with enhanced conversions. This data may be on the conversion page or on a previous page 3. Use your mouse to right-click on top of it and select **Inspect** 4. You will see the **Developer** **Tools** launch within your browser 5. Within the source code presented in the Developer Tools page, you will see highlighted code. This code is the page element where you need to extract CSS Selectors for the customer data your right-clicked 6. Depending on your browser, select **Copy Selector** 7. In the other tab, with Google Tag Manager open, paste that text in the **Element selector** field 1. For reference, it should look something similar to but not exactly like this `tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > custEmail` 8. Click **Save** 9. Repeat the steps above for all the user-provided data you want to send with enhanced conversions 10. In Google Tag Manager, click **Save** ## Set up enhanced conversions using Code in Google Tag Manager ### Identify and define enhanced conversions variables In this step, the goal is to make sure the variables needed like email, address are available on the conversion page where the Google Ads conversion tag fires. If you are unsure which page this is, contact your developer. Once you have confirmed which information is available on the conversion event page, you will need to store this data inside a global Javascript variables. You can either send unhashed data, which Google will normalize and hash before the data reaches the servers. Or, you can normalize and hash data yourself. ### Hashing and normalizing data If you decide to normalize and hash the data yourself, follow these instructions: ### Normalization: - Remove leading or trailing whitespaces - Convert the text to lowercase - Format phone numbers according to the [E.164 strandard](https://en.wikipedia.org/wiki/E.164) ### Hashing - Use hex [SHA256](https://support.google.com/google-ads/answer/9004655) The following table lists more information about customer data variables. You can name the variables as you prefer and use the `Key Name` column to know how said variable should be referenced in the enhanced conversions tag. Please note, all data shoud be passed as `String` type variable. | **Data Field** | **Key Name** | **Description** | | --- | --- | --- | | Email address | `email` | User email. | Email address | `sha256_email_address` | Hashed user email. | Phone number | `phone_number` | User phone number. Must be in [E.164 format](https://en.wikipedia.org/wiki/E.164), which means it must be 11 to 15 digits including a plus sign (+) prefix and country code with no dashes, parentheses, or spaces. | Phone number | `sha256_phone_number` | Hashed user phone number. | First name | `address.first_name` | User first name. | First name | `address.sha256_first_name` | Hashed user first name. | Surname | `address.last_name` | User last name. | Surname | `address.sha256_last_name` | Hashed user last name. | Street address | `address.street` | User street address. Example: '123 New Rd' | | City | `address.city` | User city name. Example: `Southampton’ | | Region | `address.region` | User province, state, or region. Example: `Hampshire’ | | Postal code | `address.postal_code` | User post code. Example: 'SO99 9XX' | | Country | `address.country` | User country code. Example: 'UK'. Use 2-letter country codes, per the ISO 3166-1 alpha-2 standard. | The next steps will be to enable enhanced conversions in Google Tag Manager and reference the customer data variables. ### Enable enhanced conversions in Google Tag Manager 1. Sign in to Google Tag Manager, click **Workspace** then click **Tags** from the navigation menu 2. Select the Google Ads conversion tracking tag and edit the tag 1. If you have not set up your Google Ads conversion tracking tag, you can read more about that in [Google Ads conversions](https://support.google.com/tagmanager/answer/6105160) 2. Make sure this conversion action has the same conversion tracking ID and label as the conversion as the conversion action you enabled enhanced conversions for in Google Ads 3. Click **Include user-provided data from your website** 4. Click **Select user-provided data variable**, the select **New Variable** 5. In the new `User Provided data variable` , select **Code** at the bottom 6. Under `Choose Variable Type` , select **Custom Javascript** 7. Copy the following code in the custom Javascript variable ```javascript function () { return { "email": yourEmailVariable , // replace yourEmailVariable with variable name that captures your user’s email "phone_number": yourPhoneVariable , // repeat for yourPhoneVariable and following variable names below "address": { "first_name": yourFirstNameVariable , "last_name": yourLastNameVariable , "street": yourStreetAddressVariable , "city": yourCityVariable , "region": yourRegionVariable , "postal_code": yourPostalCodeVariable , "country": yourCountryVariable } } } ``` <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> 💡 <b>Note</b>: you can also hardcode the field with a string or use a function instead of using variables </aside> ### Code sample for normalized and hashed variables ```javascript gtag("set", "user_data", { sha256_email_address: yourNormalizedandHashedEmailVariable, sha256_phone_number: yourNormalizedandHashedPhoneVariable, address: { "address.sha256_first_name": yourNormalizedandHashedFirstNameVariable, "address.sha256_last_name": yourNormalizedandHashedLastNameVariable, city: yourCityVariable, region: yourRegionVariable, postal_code: yourPostalCodeVariable, country: yourCountryVariable, }, }); ``` As mentioned above, the phone number must be in E.164 format, which means it must be 11 to 15 digits including a plus sign (+) prefix and country code with no dashes, brackets or spaces. 1. For each type of customer information in the code above, replace the placeholder variables with the name of the global Javascript variable containing the piece of customer data on the conversion page 1. If your site does not collect a field, remove the field entirely from the code rather than leaving it blank. For instance, if your website only collects emails, your Javascript variable would look like this: ```javascript function(){ return { "email": yourEmailVariable } } ``` 2. Click **Save** The next step is validate that enhanced conversions is working properly. ## Validate Enhanced Conversions Implementation To verify your implementation, navigate the conversion page and follow the steps below. Please test your implementation as soon as you are done with your set up. ### Validate the implementation using Developer Tool 1. Rick click on your web page 2. Click **Inspect** and select the **Network** tab 3. Make sure Network activity is being recorded 4. Enter `google` in the search bar 5. Find the network request that is going to `googleadservices.com/pagead/conversion` or `google.com/pagead/1p-conversion` 6. Click **Payload** to view the list of query string parameters 7. Look for a parameter `em` with a hashed string as the value. The value should start with `tv.1~em` followed by a long string of characters. If you see the `em` parameter, this means that the enhanced conversion tag is picking up and hashing the enhanced_conversion_data object <aside style="background: #f1f1ef; width: 100%; border-radius: 4px; padding: 12px 16px 12px 12px"> 💡 If you see the `em` parameter but you only see `tv.1~em` without a long hashed sting following it then you are sending the enhanced conversions parameter but it is empty. This may happen if user-provided data is not available at the time of conversion. </aside> ### Review the Diagnostics report to confirm your implementation (after 48 hours) After 48 hours of implementing enhanced conversions, you’ll be able to view the tag diagnostics report in Google Ads, which you can use to validate that the implementation is working properly. Follow these instructions to get there: 1. In the Google Ads account, click the Goals icon 2. Click the Conversions drop down in the section menu, then click Summary 3. Click the conversion action that has enhanced conversions enabled 4. Select Diagnostics from the page menu at the top. You’ll be able to see your enhanced conversion tag diagnostics report and the metrics for your enhanced conversion in each section 5. Review the various health checks to make sure everything is working as expected 6. If the tag diagnostics report notified you that something may be wrong, follow the instructions in the notification and the Help Center to troubleshoot

How to Implement User ID Tracking in Google Analytics 4 with HubSpot Forms and Google Tag Manager

June 2, 2024

When working with Google Analytics 4, cross-platform and cross-device measurements are key to gain a good understanding of your users' journeys. and have more precise measurement over new / returning users. Google Analytics 4 allows for such measurements to happen through the user_id. However, the challenge with the user_id is that it was designed to be used with websites where users are able to login and logout. One of the main reasons why this feature is recommended to be used with such systems is because it allows analysts to set a unique identifier from a system they can trust such as a database ID. While it is understandable, this restricts multiple marketing websites from using this feature since users, usually, can neither log in nor log out on such websites. But, what these websites have is forms used for lead generation. And, in this tutorial, we will discover how using said forms you can set up user ID tracking for your Google Analytics 4 property. In this guide, we will use Hubspot for our forms and Google Tag Manager for setting up the tracking. ## Pre-requisites 1. A Hubspot form; regardless of its nature 2. A Google Tag Manager account 3. Access to source code (optional) ## Setting up the user_id As explained above, since marketing websites do not offer a way to generate a database-like user ID, we will need to get creative and find a system that allows us to get a similar result. To reiterate, the reason a database-like user ID is preferred is because it is unique for every user and remains the same for an identified user. This allows systems to precisely identify users without, or very minimal, risk of facing a mistake. So, if we want to have similar functionality, the goal would be to implement a one way hash that returns the same result for the same string and such a hash is the _MD5_. This algorithm allows us to create a unique hash for each user upon form submission. Once this hash is available, we are going to set it as the user_id. ## User ID Specifications Before we dive into the technical implementation, let’s have a look at the limits of the user ID: - The User-ID feature is built for use with Google Analytics technologies. All implementations must comply with the [Analytics SDK / User-ID Feature Policy](https://developers.google.com/analytics/devguides/collection/app-web/policy). - The user IDs you send to Google Analytics must be fewer than 256 characters long. - Any data in your Analytics account collected and recorded prior to implementation won't be reprocessed and associated with a user ID. - User-ID data collected in one property can't be shared or mixed with data in other properties. ## User ID Tracking The first step to the tracking of the user ID is ensuring that we can track the forms where users can submit their emails. In this tutorial, we are going to be using Hubspot forms. If you are not using Hubspot for your forms, the logic remains the same. ### Tracking Hubpost Form Here’s how we can track form submissions for Hubspot forms: ```js window.addEventListener("message", function (event) { if ( event.data.type === "hsFormCallback" && event.data.eventName === "onFormSubmitted" ) { window.dataLayer.push({ event: "form_submitted", form_id: event.data.id, }); } }); ``` 1. `window.addEventListener("message", function (event) { ... });`: This line adds an event listener for message events. These events are dispatched by the HubSpot forms when a form is submitted. 2. `if (event.data.type === "hsFormCallback" && event.data.eventName === "onFormSubmitted") { ... }`: This condition checks if the message event is of the type hsFormCallback and if the event name is onFormSubmitted, indicating a form submission. 3. `window.dataLayer.push({ event: "form_submitted", form_id: event.data.id, });`: If the condition is met, this code pushes an event to the dataLayer with the event name form_submitted and the form ID. We need to modify this code to capture the submitted value of the email field. ```js window.addEventListener("message", function (event) { if ( event.data.type === "hsFormCallback" && event.data.eventName === "onFormSubmitted" ) { window.dataLayer.push({ event: "form_submitted", form_id: event.data.id, user_email: event.data.data.submissionValues.email, }); } }); ``` `user_email: event.data.data.submissionValues.email`: This line extends the previous code by also capturing the submitted email value and adding it to the dataLayer event. Here’s an example of the code’s output: ```json { "event": "form_submitted", "form_id": "366c848d-26e5-4294-b185-9bab4cc661e6", "user_email": "example@email.com" } ``` ## Hashing Submitted Email Value The next step is now to hash the user email in order to create our user ID. To do so, we will first need to add a piece of code that will allows us to hash the email ```js !(function (n) { "use strict"; function d(n, t) { var r = (65535 & n) + (65535 & t); return (((n >> 16) + (t >> 16) + (r >> 16)) << 16) | (65535 & r); } function f(n, t, r, e, o, u) { return d(((u = d(d(t, n), d(e, u))) << o) | (u >>> (32 - o)), r); } function l(n, t, r, e, o, u, c) { return f((t & r) | (~t & e), n, t, o, u, c); } function g(n, t, r, e, o, u, c) { return f((t & e) | (r & ~e), n, t, o, u, c); } function v(n, t, r, e, o, u, c) { return f(t ^ r ^ e, n, t, o, u, c); } function m(n, t, r, e, o, u, c) { return f(r ^ (t | ~e), n, t, o, u, c); } function c(n, t) { var r, e, o, u; (n[t >> 5] |= 128 << t % 32), (n[14 + (((t + 64) >>> 9) << 4)] = t); for ( var c = 1732584193, f = -271733879, i = -1732584194, a = 271733878, h = 0; h < n.length; h += 16 ) (c = l((r = c), (e = f), (o = i), (u = a), n[h], 7, -680876936)), (a = l(a, c, f, i, n[h + 1], 12, -389564586)), (i = l(i, a, c, f, n[h + 2], 17, 606105819)), (f = l(f, i, a, c, n[h + 3], 22, -1044525330)), (c = l(c, f, i, a, n[h + 4], 7, -176418897)), (a = l(a, c, f, i, n[h + 5], 12, 1200080426)), (i = l(i, a, c, f, n[h + 6], 17, -1473231341)), (f = l(f, i, a, c, n[h + 7], 22, -45705983)), (c = l(c, f, i, a, n[h + 8], 7, 1770035416)), (a = l(a, c, f, i, n[h + 9], 12, -1958414417)), (i = l(i, a, c, f, n[h + 10], 17, -42063)), (f = l(f, i, a, c, n[h + 11], 22, -1990404162)), (c = l(c, f, i, a, n[h + 12], 7, 1804603682)), (a = l(a, c, f, i, n[h + 13], 12, -40341101)), (i = l(i, a, c, f, n[h + 14], 17, -1502002290)), (c = g( c, (f = l(f, i, a, c, n[h + 15], 22, 1236535329)), i, a, n[h + 1], 5, -165796510, )), (a = g(a, c, f, i, n[h + 6], 9, -1069501632)), (i = g(i, a, c, f, n[h + 11], 14, 643717713)), (f = g(f, i, a, c, n[h], 20, -373897302)), (c = g(c, f, i, a, n[h + 5], 5, -701558691)), (a = g(a, c, f, i, n[h + 10], 9, 38016083)), (i = g(i, a, c, f, n[h + 15], 14, -660478335)), (f = g(f, i, a, c, n[h + 4], 20, -405537848)), (c = g(c, f, i, a, n[h + 9], 5, 568446438)), (a = g(a, c, f, i, n[h + 14], 9, -1019803690)), (i = g(i, a, c, f, n[h + 3], 14, -187363961)), (f = g(f, i, a, c, n[h + 8], 20, 1163531501)), (c = g(c, f, i, a, n[h + 13], 5, -1444681467)), (a = g(a, c, f, i, n[h + 2], 9, -51403784)), (i = g(i, a, c, f, n[h + 7], 14, 1735328473)), (c = v( c, (f = g(f, i, a, c, n[h + 12], 20, -1926607734)), i, a, n[h + 5], 4, -378558, )), (a = v(a, c, f, i, n[h + 8], 11, -2022574463)), (i = v(i, a, c, f, n[h + 11], 16, 1839030562)), (f = v(f, i, a, c, n[h + 14], 23, -35309556)), (c = v(c, f, i, a, n[h + 1], 4, -1530992060)), (a = v(a, c, f, i, n[h + 4], 11, 1272893353)), (i = v(i, a, c, f, n[h + 7], 16, -155497632)), (f = v(f, i, a, c, n[h + 10], 23, -1094730640)), (c = v(c, f, i, a, n[h + 13], 4, 681279174)), (a = v(a, c, f, i, n[h], 11, -358537222)), (i = v(i, a, c, f, n[h + 3], 16, -722521979)), (f = v(f, i, a, c, n[h + 6], 23, 76029189)), (c = v(c, f, i, a, n[h + 9], 4, -640364487)), (a = v(a, c, f, i, n[h + 12], 11, -421815835)), (i = v(i, a, c, f, n[h + 15], 16, 530742520)), (c = m( c, (f = v(f, i, a, c, n[h + 2], 23, -995338651)), i, a, n[h], 6, -198630844, )), (a = m(a, c, f, i, n[h + 7], 10, 1126891415)), (i = m(i, a, c, f, n[h + 14], 15, -1416354905)), (f = m(f, i, a, c, n[h + 5], 21, -57434055)), (c = m(c, f, i, a, n[h + 12], 6, 1700485571)), (a = m(a, c, f, i, n[h + 3], 10, -1894986606)), (i = m(i, a, c, f, n[h + 10], 15, -1051523)), (f = m(f, i, a, c, n[h + 1], 21, -2054922799)), (c = m(c, f, i, a, n[h + 8], 6, 1873313359)), (a = m(a, c, f, i, n[h + 15], 10, -30611744)), (i = m(i, a, c, f, n[h + 6], 15, -1560198380)), (f = m(f, i, a, c, n[h + 13], 21, 1309151649)), (c = m(c, f, i, a, n[h + 4], 6, -145523070)), (a = m(a, c, f, i, n[h + 11], 10, -1120210379)), (i = m(i, a, c, f, n[h + 2], 15, 718787259)), (f = m(f, i, a, c, n[h + 9], 21, -343485551)), (c = d(c, r)), (f = d(f, e)), (i = d(i, o)), (a = d(a, u)); return [c, f, i, a]; } function i(n) { for (var t = "", r = 32 * n.length, e = 0; e < r; e += 8) t += String.fromCharCode((n[e >> 5] >>> e % 32) & 255); return t; } function a(n) { var t = []; for (t[(n.length >> 2) - 1] = void 0, e = 0; e < t.length; e += 1) t[e] = 0; for (var r = 8 * n.length, e = 0; e < r; e += 8) t[e >> 5] |= (255 & n.charCodeAt(e / 8)) << e % 32; return t; } function e(n) { for (var t, r = "0123456789abcdef", e = "", o = 0; o < n.length; o += 1) (t = n.charCodeAt(o)), (e += r.charAt((t >>> 4) & 15) + r.charAt(15 & t)); return e; } function r(n) { return unescape(encodeURIComponent(n)); } function o(n) { return i(c(a((n = r(n))), 8 * n.length)); } function u(n, t) { return (function (n, t) { var r, e = a(n), o = [], u = []; for ( o[15] = u[15] = void 0, 16 < e.length && (e = c(e, 8 * n.length)), r = 0; r < 16; r += 1 ) (o[r] = 909522486 ^ e[r]), (u[r] = 1549556828 ^ e[r]); return ( (t = c(o.concat(a(t)), 512 + 8 * t.length)), i(c(u.concat(t), 640)) ); })(r(n), r(t)); } function t(n, t, r) { return t ? (r ? u(t, n) : e(u(t, n))) : r ? o(n) : e(o(n)); } "function" == typeof define && define.amd ? define(function () { return t; }) : "object" == typeof module && module.exports ? (module.exports = t) : (n.md5 = t); })(this); ``` 1. **MD5 Implementation**: This function implements the MD5 hashing algorithm, which is used to generate a unique hash from the user's email. The detailed implementation is beyond the scope of this tutorial, but you can refer to MD5 documentation for more information. 2. **Usage**: This function is called later to hash the user's email. And this is the output: ```json { "event": "form_submitted", "form_id": "366c848d-26e5-4294-b185-9bab4cc661e6", "user_id": "9ad6c92b4795ffc23a27d805ad7421ef" } ``` **You can add this code either as custom HTML tag in Google Tag Manager or directly within your code base.** ## Creating the Google Analytics 4 Tag Using the following steps, we will create a GA4 Event tag that will track the form submission event and set the user id. 1. Create a New Tag: 1. In your GTM workspace, click on Tags in the left-hand menu. 2. Click on the New button to create a new tag. 2. Configure the Tag: 1. Click on Tag Configuration. 2. Select Google Analytics: GA4 Event. 3. Select GA4 Configuration Tag: 1. Choose the GA4 Configuration Tag you have previously set up. If you haven't set one up, you'll need to create it by selecting New Tag, then Google Analytics: GA4 Configuration, and entering your GA4 Measurement ID. 4. Name Your Event: 1. Enter the event name under Event Name. The value you should enter is {{Event}} . This will capture the event parameter’s value in the dataLayer. In our case, the value is form_submitted 5. Add Event Parameters: 1. Click on Add Parameter , then enter form_id and in the Value field, enter {{DLV - Form ID}} . 2. Again click on Add Parameter , then enter user_id and in the Value field, enter {{DLV - User ID}} . 6. Set Up Triggers: 1. Click on Triggering to select when the tag should fire. 2. Create a custom event trigger and in the event name enter form_submitted 7. Save the Tag: 1. After configuring the tag and trigger, click Save. **Note: you still need to create 2 dataLayer variables with the name DLV - Form ID and DLV - User ID with their respective values being form_id and user_id** Don’t forget to create a user-level custom dimension with the same value as the Google Tag which in our case would be hashed_user_id. After configuring the tag don’t forget to save and publish your workspace. ## Conclusion With these additions, you are now able to track your users’ journey across platforms and devices through generating a unique ID using the MD5 algorithm.

Implementing Google Consent Mode using CookieYes

August 17, 2024

Data collection on the web has changed over the years, and while it is easier now with many tools at our disposal, it is also harder; in a sense. With data privacy laws becoming stricter and stricter, data collection and measurements have to keep up in order to stay compliant and offer the users the best experience; an honest experience that lets them know what data will be collected and what data will be tied back to them. In today's post, we will look at how you can make sure your data collection and web measurement comply with the users' consent choices. The tools we are going to be using are CookiYes as the Cookie Consent Solution, Google Consent Mode, Google Tag Manager, and Google Analytics 4. Before we begin though, let's define some terms so that everything is clear. ## Consent Mode & Cookie Consent Solution ### Cookie Consent Solution A cookie consent solution refers to a set of tools, practices, and functionalities used by website owners and operators to obtain user consent for the use of cookies and similar tracking technologies on their websites or online platforms. Cookies are small pieces of data that are stored on a user's device (such as a computer or smartphone) when they interact with a website. These cookies serve various purposes, including analyzing user behavior, remembering login credentials, and providing personalized experiences. A cookie consent solution typically involves the following components: 1. **Consent Banner/Popup**: When a user visits a website, a banner or popup is displayed informing them about the use of cookies and similar tracking technologies. This banner typically includes information about the types of cookies used, their purposes, and a link to the website's privacy policy. 2. **Consent Management Platform (CMP)**: A consent management platform is a tool that allows website owners to create, customize, and manage their cookie consent banners and user preferences. It enables users to provide or withdraw consent for different types of cookies and allows website operators to track and document user consent. 3. **Granular Consent Options**: A good cookie consent solution provides users with granular options to choose which types of cookies they want to consent to. This could include different categories such as functional cookies, analytical cookies, advertising cookies, and more. 4. **Link to Privacy Policy**: The consent banner typically includes a link to the website's privacy policy, which provides detailed information about how the website collects, processes, and shares user data. 5. **Opt-Out Mechanism**: Users should have the ability to change their consent preferences or withdraw their consent at any time. The consent solution should provide an easy way for users to opt-out of specific cookie categories or all cookies. 6. **Documentation and Reporting**: The solution should allow website owners to keep records of user consents and provide reporting features to demonstrate compliance with relevant data protection regulations. 7. **Responsive Design**: The consent banner and user interface should be designed to work well on different devices, such as desktops, tablets, and smartphones. Implementing a robust cookie consent solution helps website operators respect users' privacy rights, maintain legal compliance, and build trust by being transparent about their data collection and usage practices. ## Google Consent Mode Consent Mode enables you to inform Google about your users' consent status for cookies or app identifiers. This way, tags can adjust their behavior accordingly, respecting users' choices. When you integrate Consent Mode with your Consent Management Platform (CMP) or custom setup, like a cookie consent banner, it seamlessly receives users' consent preferences. Consent Mode then dynamically adjusts the behavior of Google Analytics, Ads, and third-party tags that rely on cookies. In cases where users deny consent, instead of storing cookies, tags send signals (pings) to Google. If you are using Google Analytics 4, any gaps in data collection are supplemented by conversion modeling and behavioral modeling techniques used by Google. ## Implementing CookieYes Now that the definitions are out of the way, we can get started with the implementation. The first step is to implement our cookie banner. As mentioned above, we are going to be using CookieYes as our CMP. Let's get into it. The first thing to do is to head to [CookieYes's website](https://www.cookieyes.com/) and sign up for a free trial. Once your account is created, the next step is to follow up the instructions to style the cookie banner. You can customize the cookie banner or proceed with the default template. You can always customize the banner later by heading to **Dashboard > Consent banner**. Once you are done with the set up, click next step. At this point, you should see a popup containing the installation script for your cookie banner. The only thing left is to install the script, and test it. CookieYes has a lot of integration with CMS and website builders and if you are using one, check the list because the installation may differ a bit: ### CMS & Website Builders Integrations - Angular - React.js - Vue.js - Nuxt.js - Gatsby - Next.js - Kartra - Kajabi - MODX - ImpressPages - Shopify - Weebly - Magento - Squarespace - Wix - WordPress - Joomla - Drupal - Blogger If you do not see your CMS or website builder, do not worry. Simply, copy/paste the installation script in your head element and put it as high as possible. Once you the installation is done, verify your installation. If you are having an issue, feel free to connect with support. ## Google Tag Manager & Consent Mode Once the cookie banner has been implemented, it is time to continue the action with Google Tag Manager and the Consent Mode. Before we continue with Google Tag Manager, we need to confirm that our banner has been properly set up. To do that, head to you website and check if you are able to see the consent banner. Once you see the consent banner, confirm your choice. It does not matter what you select, so long as you make a choice. Once your choice is made, open the Web Developer Tools and find the cookies section; if you are using Firefox this should be in the Storage section. You should be seeing something as follows: ![alt text](./images/cookieyes-cookies.png) The cookie name is equal to cookieyes-consent. If you are able to see this cookie, then you are ready to continue. If you cannot see this cookie, please make sure to go over the implementation steps until you are able to see this cookie. Proceeding without this cookie will cause errors. Time to move to Google Tag Manager (GTM). In GTM, head to the variable section, and create a new **User-Defined** variable. Select 1st Party Cookie as the type of the variable and type **cookieyes-consent** inside the Cookie Name input field. Also, make sure to check URI-decode cookie option. Your window should look something like this: ![alt text](./images/cookies-yes-variable-creation-gtm.png) Add a name distinctive name to your variable and save it. Essentially, what we just did is create a Google Tag Manager variable that will have the same value as our cookiyes-cookie. This is going to be very useful as it will help us avoid reading the value of the cookie with JS everytime we need it. The next step is to preview the value of said varibale. To do so, we need to preview our workspace. Once your debugging window loads, select any event (window loaded for instance) and look for your variable in the variables tab. You should see something similar to this: ![alt text](./images/cookieyes-variable-in-gtm.png) If you are not able to see a value attached to your cookie, try another event. If you are still seeing nothing, delete the variable and go over the creation steps one more time. Now that we have a value attached to our cookie, it is time to decode the value to understand how we can translate it into consent. Let's have a look at the value: ``` consentid:c3hRRGJBeGJucEdMd0l4ZlpoclhIS3ZoRFRIaXN5TkY, consent:no,action:,necessary:yes,functional:no,analytics:no,performance:no,advertisement:no,other:no ``` First things first, it is important to note that this is a string. Second, this string contains the state of the consent. Third, this value will update as the use updates their consent preferences. Fourth, the part that will be using to capture the user's consent from this string is the following: ``` necessary:yes,functional:no,analytics:no,performance:no,advertisement:no,other:no ``` This substring contains the user's consent preferences regarding: - Necessary cookies - Functional cookies - Analytics cookies - Performance cookies - Advertisement cookies - Other cookies The next task is to transform this substring into variables we can use to store the user's consent. Variables that we can update we every time the consent changes. To do this, we will use good old JavaScript. Here's a code snippet that allow us to create a variable that stores the user's consent regarding advertising cookies: ```js function returnAdvertisementConsent() { var cookieYesConsentCookie = {{var_cookieYesConsentCookie}}; var grantedSubstring = ',advertisement:yes,'; if (!cookieYesConsentCookie || cookieYesConsentCookie.indexOf(grantedSubstring) === -1) { return 'denied'; } return 'granted'; } ``` Please note that you will have to replace {{var_cookieYesConsentCookie}} with your 1st Party Cookie name created in the steps above. The code snippet checks if cookieYesConsentCookie is not falsy (this does not mean false) and if the value of cookieYesConsentCookie does not include ,advertisement:yes,. If any of these conditons are met, our variable will have the value denied. Else, the variable will have the value granded. In Google Tag Manager, create a new User-Defined variable and choose Custom JavaScript as the variable type. Paste the code snippet above, name your variable and save it. Once your variable is created, repeat the same steps for each consent type you would want to capture. Do not forget to change the value of the grantedSubstring variable to reflect the consent your need. For instance, if you are intrested in capturing Analytics consent, then the variable should be declared as follows: ```js var grantedSubstring = ",analytics:yes,"; ``` Once all of your variables have been defined, it is time to check if they have been correctly declared. For our first test, we will be accepting all cookies and check if all of our declared variables will have granted as their value. Preview the workspace where you have applied your changes, accept all cookies and take a look the variables section. Please remember to check all the variables you have created to check if you are seeing the correct values. Once you have confirmed that everything looks good, it is time for the second test. Before doing so, delete the cookieyes-consent cookie. Once that has been done, close the debugging window and preview your workspace again. You should see the consent banner showing up, and this time reject all cookies. Please note that necessary cookies will always be accepted as they are required for the website to properly function. Once you have confirmed that the test has been successful, it is recommended to run couple of other tests where you accept some cookie categories and deny others just to ensure that everything is working as expected. Notice that we have not used the Consent Mode just yet. We will get to that part later on but fow now, we still have some configuration to do in Google Tag Manager before enabling the consent mode. Now that we have our variables ready, we need to figure out a way to let Google Tag Manager update the variables once the user has changed their consent to make sure our events fire in respect to the user's consent. So far, if the user changes their consent, there is no way to communicate that to Google Tag Manager. This means that we are able to capture the status of the consent when the user first lands on the site, but we will not be able to adapt to their preferences if they change them. To do this, we will be using a tag that allow us to update our variables to adapt to the new consent preferences. ## Consent Mode (Google tags) The tag we will be using to update our consent variables is Consent Mode (Google tags) made by Simo Ahava. Head to the tags section, and click on New. In the Tag Type, click on Discover more tag types in the Community Template Gallery. Use the search functionalily to find the right tag and click on Add to workspace. Once the tag has been added, it's time to configure it. The first step is to rename the tag. It's suggested that you give a distinctive name. The second step is to change the Consent Command to Update since that's the purpose of tag. The third step to map the different variables to their respective consent. Google Tag Manager has 4 consent types (read more about them in [consent types](https://support.google.com/tagmanager/answer/10718549?hl=en)): - Advertising - Analytics - Personalization - Functionality - Security It is important to map the right variables to the right cosent types to make sure that no expected behaviour arises. That being said, make sure to read Google's official documentation on the consent types to make sure you have a firm grabs over the available options and their meaning. Once the mapping is completed, the next step is to configure the tag's trigger. Add the trigger, name it and select the Initialization as trigger type. This will ensure that every time the our tag designed to update our consent variables fires before any other tag would. Save the trigger as well as the tag, and it is time to preview the workspace to make sure that our changes are effective. Before doing that, delete the cookieyes-cookie to make sure that the old consent preferences are not interfering. For our testing scenario, we will deny all cookies at first, and the accept the analytics cookies. Remember, the goal at this point is to see if our variables are updating correctly. When the debugging window loads and you the consent banner loads, make sure to reject all cookies. Time to check the variables. From the left hand side, select the event Initialization, select the Variables tab and search for the consent variable. The next step is to update the consent to see if we are able to capture it. To do so, click on the cookie settings button, toggle the Analytics cookies on and click on Save My Preferences. Next, reload the page (we'll get into this at a later stage). Once the page has loaded, check the new Initialization event and look at the consent variables. If you are able to see something similar to this then your implementation is correct. The next step is to simply run other test scenarios where you accept and reject different consent types to make sure everything is behaving according to plan. Once you have confirmed that all variables are updating correcrtly, it's time to activate the Consent Mode. **Please remember to refresh the page after updating your consent preferences.** ## Consent Mode Activation Since we have our consent variables ready and mapped to the different consent types, it is time to use them to inform Google Tag Manager how to trigger our tags in accordance with the users' consent preferences.To enable this feature in your container: 1. In Tag Manager, click Admin > Container Settings. 2. Under Additional Settings, select Enable consent overview. Once this setting is turned on, in the Tags section, you should be able to see a new shield-looking icon next to the New button. Click the Consent Overview icon to open the Consent Overview page. You should notice two sections: - Consent Not Configured: contains all the tags where consent has not been configured - Consent Configured: contains all the tags where consent has been configured ## Tag Consent Configuration There are two ways to configure consent for tags, one by one or in bulk. We'll discuss both methods, but feel free to choose the most suitable one for your ends. ## Tag Consent Configuration - Individual To configure consent for an individual tag, select the tag in question and in the tag configuration section, scroll down to Consent Settings and expand the dropdown section. You will notice two sections: - Built-in Consent Checks: this tag checks for the status of all consent types listed below. Tags with built-in consent checks typically modify their behavior based on the consent granted. - Additional Consent Checks: - Not set - No additional consent required: select this option to indicate that your tag does not need to check for additional consent in order to fire. You may choose to use this option to distinguish tags that you have decided need no additional consent from tags that you have not yet reviewed. - Require additional consent for tag to fire:This tag will only fire if the status of all of the specified consent types is 'granted' when the tag is triggered. It is important to note that there is a difference between choosing No additional consent is required and Require additional consent for tag to fire. The difference is essentially what is referred to as cookieless pings. You can read more about those [here](https://support.google.com/analytics/answer/9976101?hl=en). For our set up we will set up our tags to required additional consent before firing. ## Tag Consent Configuration - Bulk This is the option you should probably use if your container has a lot of tags that you would need to configure consent for. To configure consent in bulk, click on the Consent Overview Icon to open the Consent Overview page. Next, select the tags that should have a similar consent type and click on the Edit Consent Settings button (next to the search icon). This will open a popup containing the same configuration settings you saw when configuring the consent on an individual tag. As mentioned above, we will set our tags to required additional consent. ## Testing the consent configuration Once you have configured the consent for your tags, it is time to test it to make sure that everything is working correctly. Again, delete the cookieyes-consent cookie before starting to make sure the previous settings are not interfering with your tests. Preview your workspace, and begin the test. For the initial test, refuse both advertising and analytics tags and interact with buttons or link being tracked to see if they are firing. You can even check if the Google Analytics 4 Configuration Tag has fired or not. The short answer is no. Since we refused both analytics and advertising, the configuration tag (and all other GA4 related events should not be firing). Here's what you should see on your end: ![alt text](./images/blocked-tag-due-to-consent.png) When you test other events related to both the analytics consent type as well as advertising consent type, you should see the tags under the Tags Blocked by Consent Settings tab. The next step is to update your configuration settings to allow all tracking. This time do not reload the page. Try to click on some CTA or links, are the tags firing? Are they still being blocked? Yes, they are. And this should be the last piece of the puzzle to have everything running smoothly. ## Updating Consent Outside Page Loads - CookieYes Support GCM So far, whenever we wanted the updated consent to enter in effect, we had the update the page. This means that between the consent preferences update and the second, the actions of the user will be tracked even when they are not supposed to, or they will be not tracked even if they are supposed to. There are two ways we can solve this issue. The first one is to force the page to load whenever consent has been updated. Remember that we are updating our consent variables on initialization, but we need to page to load or reload for this update to happen. And by forcing the update, you will always ensure that your consent variables are up to date with the user's preferences. The second option is to take advantage of the CookieYes Support GCM option. This option, when turned on, will send a dataLayer event that we can use to update our variables using the Consent Mode tag. To do this, follow these steps: - Log into your CookieYes account - Click on Site Settings in the navigational menu - In the Google cosent mode (GCM) section, toggle on the Support GCM option That's it! It's that easy. Now, it is time to take this new setup for a test spin. Go back to Google Tag Manager and preview your workspace. Click on the Cookie Settings button, and update your consent preferences, and head to the GTM debugging window. Notice anything different? There is now a new event pushed into the dataLayer: cookie_consent_update. ```js dataLayer.push({event: "cookie_consent_update", gtm.uniqueEventId: 167}) ``` So now that this event is being logged into the dataLayer, we can use it to update our consent variables every time the user is updating their preferences. To do so, we will need to make sure that our Consent Mode tag is firing on both the initialization of the page and the cookie_consent_update event. To do this, head to Google Tag Manager, select Consent Mode tag, click on the Triggering section and finally click on the + button to create a new trigger. In the Choose trigger window, click on the + button to create a new trigger and choose Custom Event as the trigger type. In the event name input field, type in cookie_consent_update. Name the tigger and save it. Now the Consent Mode tag should have to triggers. It's time to test the implementation. It's the same drill. Preview the workspace, choose your consent, make sure nothing fires if it is not supposed to. Then, update the consent preferences and interact with the page without navigating away at first to make sure that the new consent is in place. If everything is working according to plan, congratulations! You have successfully implemented the Consent Mode on Google Tag Manager and your tags are now triggering accoriding to users' consent preferences.

Implementing Google Consent Mode using gtag.js and CookieYes

April 4, 204

In this new article covering the Consent Mode (and, Consent Mode V2) we will be covering how you can implement Consent Mode using gtag.js and CookieYes. However, whatever we will discuss in this article can be used to implement Consent Mode with any other Consent Management Platform. ## Pre-requisites Before we get started, there are only two things you need to get started with the implementation: 1. A CookieYes published script 2. Familiarity with JavaScript We recommended familiarity with JavaScript in many of our articles, but for this tutorial it is highly recommended that you are familiar with JavaScript as we will be using the gtag.js library. In any case, here's a quick overview that provides the necessary details. ## gtag.js Refresher gtag.js serves as a unified framework that allows websites to send data to Google Analytics, Google Ads, and Google Marketing Platform. It streamlines the integration of these services, enabling efficient tracking of user interactions, site analytics, and ad performance. For Consent Mode implementation, gtag.js is crucial as it manages the consent states—what data can be collected and processed based on user consent. A clear understanding of gtag.js commands, especially related to consent ('consent', 'default', and 'consent', 'update'), is essential. These commands help in setting up the initial consent state and updating it as users modify their preferences. ## Why understanding gtag.js & JavaScript is important The implementation of Consent Mode heavily relies on using JavaScript to manipulate the consent states within gtag.js. Here’s why understanding both is crucial: - **JavaScript Fundamentals**: A good grasp of JavaScript basics, such as variables, functions, and event handling, is necessary to implement the consent logic effectively. - **gtag.js Operations**: Understanding how gtag.js works, including its consent commands, helps in configuring and updating consent settings accurately in response to user interactions. ## The Consent Command The first step in the implementation is going over the consent commands of the gtag.js library. These commands will allow us to set the default consent and update it as users change their preferences. Here's what a consent command looks like: ```js gtag('consent', <default || update>, { <consent categories> }); ``` If this doesn't make sense, don't worry we will be looking into these commands in detail as the tutorial progresses. ## Setting Default Consent Implementing Consent Mode (V1 or V2) can be summarized into 2 actions: 1. Set default consent state before the user grants consent 2. Update the consent state based on the user interactions with the consent settings So, let's explore how we can set the default consent. In this tutorial, we will deny consent by default for all the parameters. You can modify this to suit your implementation by switching whatever is set to denied to granted. ```js // Match ad_storage and ad_user_data to ad_storage gtag("consent", "default", { ad_storage: "denied", ad_user_data: "denied", ad_personalization: "denied", analytics_storage: "denied", personalization_storage: "denied", functionality_storage: "denied", security_storage: "granted", // These are necessary cookies so they are always set to granted by default }); ``` Let's break down each part of the command: 1. **gtag('consent', 'default', {...})**: 1. gtag: This is the function call to Google's global site tag (gtag.js) library 2. 'consent': This indicates that the command is related to user consent settings. 3. 'default': This specifies that the provided settings should be applied as the default consent state for all types of storage and data handling. 2. **Inside the {...} are the specific consent configurations**: 1. 'ad_storage': 'denied': Denies consent for storage related to advertising, like cookies that track users for advertising purposes. 2. 'ad_user_data': 'denied': Denies consent for using user data in advertising, which might include demographic details, interests, and other ad-targeting information. 3. 'ad_personalization': 'denied': Denies consent for personalizing ads based on user behavior and preferences. 4. 'analytics_storage': 'denied': Denies consent for storing data related to analytics, like cookies that track user interactions with the website. 5. 'personalization_storage': 'denied': Denies consent for storing data used for personalizing the user experience on the site. 6. 'functionality_storage': 'denied': Denies consent for storing data necessary for certain website functionalities, like user preferences and settings. 7. 'security_storage': 'granted': Grants consent for storage necessary for security purposes, like cookies used to authenticate users or protect against fraudulent activity. This is typically always granted as it's essential for the website's security functions. Now that we have our command ready, it is time to wrap it inside a script tag which yields the following: ```html <script type="text/javascript"> // Define dataLayer and the gtag function. window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } // Set default consent to 'denied' as a placeholder gtag("consent", "default", { ad_storage: "denied", ad_user_data: "denied", ad_personalization: "denied", analytics_storage: "denied", personalization_storage: "denied", functionality_storage: "denied", security_storage: "granted", }); </script> ``` ## Optional: Handling asynchronous integrations with CMPs When your banner loads asynchronously, there's a chance it won't execute before your Google tags. To manage these cases, use wait_for_update with a specified time in milliseconds to determine the delay before sending data. For instance, if you want to default ad_storage to denied on a specific page, yet permit your Consent Management Platform (CMP) to modify the consent status, employ wait_for_update. In the code example below, analytics_storage is initially set to denied, and the consent tool has a 300-millisecond window to invoke **gtag('consent', 'update', ...)** prior to the activation of the tags: ```js gtag("consent", "default", { analytics_storage: "denied", wait_for_update: 300, }); ``` ## Updating Consent Now that we know how to set the default consent, it is time to explore step 2 of implementing Consent Mode which using updating the consent status. We will explore how to update this in 2 steps. The first step is studying the consent update command and the second step we will see when to call this function based on the user's interactions with the CookieYes banner. ### gtag('consent', 'update' ...) Say the user interacted with our banner and has accepted all the cookie categories, here's the command that we can use to update the consent status: ```js gtag("consent", "update", { ad_storage: "granted", ad_user_data: "granted", ad_personalization: "granted", analytics_storage: "granted", personalization_storage: "granted", functionality_storage: "granted", security_storage: "granted", }); ``` Now, all data collection related to measurement and advertising can be fired as they user gave their consent for such tracking to be activated. While this command will work if you were to copy/paste it in the console, the code is not dynamic. What if the user were to change their consent preference again? This is where the CookieYes comes in. Using an event listener, we can fire the gtag('consent', 'update' ...) command whenever, wherever the user updates their consent preferences. Let's see how we can do that: ```js // Update consent categories document.addEventListener("cookieyes_consent_update", function (eventData) { const currentConsentDetails = eventData.detail; if (typeof currentConsentDetails === "undefined") { return; } const consentObject = {}; const acceptedCookies = currentConsentDetails.accepted; const rejectedCookies = currentConsentDetails.rejected; acceptedCookies.forEach((cookieCategory) => { switch (cookieCategory) { case "analytics": consentObject.analytics_storage = "granted"; consentObject.personalization_storage = "granted"; break; case "advertisement": consentObject.ad_storage = "granted"; consentObject.ad_user_data = "granted"; consentObject.ad_personalization = "granted"; break; case "functional": consentObject.functionality_storage = "granted"; break; } }); rejectedCookies.forEach((cookieCategory) => { switch (cookieCategory) { case "analytics": consentObject.analytics_storage = "denied"; consentObject.personalization_storage = "denied"; break; case "advertisement": consentObject.ad_storage = "denied"; consentObject.ad_user_data = "denied"; consentObject.ad_personalization = "denied"; break; case "functional": consentObject.functionality_storage = "denied"; break; } }); gtag("consent", "update", consentObject); }); ``` Here's a breakdown of the code snippet: 1. Event Listener Registration: 1. document.addEventListener("cookieyes_consent_update", function(eventData) {...}): Registers an event listener for the cookieyes_consent_update event, which triggers when there are updates to cookie consent preferences. 2. Handling Event Data: 1. const currentConsentDetails = eventData.detail: Extracts the details of the consent update event, which includes information about accepted and rejected cookie categories. 2. Checks if currentConsentDetails is undefined and exits the function if true, to handle cases where no consent details are provided. 3. Consent Processing: 1. Initializes an empty consentObject to store the updated consent settings. 2. Iterates over acceptedCookies and rejectedCookies arrays from currentConsentDetails, categorizing cookies into analytics, advertisement, and functional. 4. Updating Consent Status: 1. For each accepted cookie category, it sets the corresponding properties in consentObject to 'granted'. 2. For each rejected cookie category, it sets the corresponding properties in consentObject to 'denied'. 5. Google Tag Update: 1. Calls gtag('consent', 'update', consentObject) to update the Google tags' consent configuration based on the user's choices. Please note that we did not check for personalization in the code snippet as it was mapped to the analytics_storage category. You can change this by mapping it to performance or any category of your choice. Here are the categories you can check against: - necessary - functional - analytics - performance - advertisement - other **The event listener used is proper to CookieYes. If you are using a different CMP, please refer to their documentation to see window methods to use for updating consent.** ## Putting Everything Together The order of the code here is vital. If your consent code is called out of order, consent defaults will not work. Depending on business requirements, specifics may vary, but in general, code should run in the following order: 1. Load the Google tag. This is your default snippet code. The default snippet should be updated to include a call to gtag('consent', 'default', ...). 2. Load your consent solution. If your consent solution loads asynchronously, use wait_for_update to make sure things happen in the correct order 3. If not handled by your consent solution, call gtag('consent', 'update', ...) after the user indicates consent. ```html <script> // Define dataLayer and the gtag function. window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } // Set default consent to 'denied' as a placeholder // Determine actual values based on your own requirements gtag("consent", "default", { ad_storage: "denied", ad_user_data: "denied", ad_personalization: "denied", analytics_storage: "denied", personalization_storage: "denied", functionality_storage: "denied", security_storage: "granted", }); </script> <!-- Google tag (gtag.js) --> <script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script> <script> window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag("js", new Date()); gtag("config", "TAG_ID"); </script> <!-- Create one update function for each consent parameter --> <script> document.addEventListener("cookieyes_consent_update", function (eventData) { const currentConsentDetails = eventData.detail; if (typeof currentConsentDetails === "undefined") { return; } const consentObject = {}; const acceptedCookies = currentConsentDetails.accepted; const rejectedCookies = currentConsentDetails.rejected; acceptedCookies.forEach((cookieCategory) => { switch (cookieCategory) { case "analytics": consentObject.analytics_storage = "granted"; consentObject.personalization_storage = "granted"; break; case "advertisement": consentObject.ad_storage = "granted"; consentObject.ad_user_data = "granted"; consentObject.ad_personalization = "granted"; break; case "functional": consentObject.functionality_storage = "granted"; break; } }); rejectedCookies.forEach((cookieCategory) => { switch (cookieCategory) { case "analytics": consentObject.analytics_storage = "denied"; consentObject.personalization_storage = "denied"; break; case "advertisement": consentObject.ad_storage = "denied"; consentObject.ad_user_data = "denied"; consentObject.ad_personalization = "denied"; break; case "functional": consentObject.functionality_storage = "denied"; break; } }); gtag("consent", "update", consentObject); }); </script> ``` Make sure to replace TAG_ID with your actual Measurement ID. You can load these on every page to make sure on every page in case the user consent does not persist. ## Best practices It's advisable to tailor the default consent settings to the regions where consent banners are displayed to your visitors. This approach ensures that data collection complies with regional requirements, allowing Google tags to modify their behavior as needed. Moreover, it prevents the potential loss of data in areas where consent banners are not necessary or relevant. The provided code sets the default consent settings universally, meaning it applies the same settings across all regions without tailoring them to specific areas where consent banners are displayed. ## Conclusion With the code provided above, you should be able to implement Consent Mode using gtag.js and cookieyes giving you the flexibility of setting consent right from your source code.

Implementing Google Consent Mode V2 with CookieYes

March 7, 2024

The deadline for implementing Google Consent mode V2 has passed and if you are advertising in the EU/EAA/UK, you will need this setting configured in order to continue ad personalization and remarketing capabilities for visitors from the mentioned reasons. If you fail to comply, you will lose access to these capabilities. If you are unsure where to start, in this article, we will walk through 2 ways you can implement Google Consent Mode V2 with CookieYes. ## What is Google Consent Mode V2? What's new? Google's Consent Mode version 2 (v2) represents an advancement in the original Consent Mode, enhancing how website publishers and advertisers manage explicit user consent for web tracking in compliance with privacy laws like the GDPR. The latest iteration of Consent Mode introduces significant updates for better compliance with privacy standards and to offer users more transparency and control over their data. Key enhancements include the introduction of two new parameters: - ad_user_data: This parameter captures a user's consent decision regarding the sharing of their data with Google's advertising services. The consent can either be "granted" or "denied," as determined by the user's interaction with the site's cookie consent banner. A "denied" status means Google's advertising tags will not collect or transmit identifiable user information. - ad_personalization: Reflecting a user's choice on ad personalization and remarketing, this parameter shows whether a user has agreed to or declined these features. Based on the user's consent, captured through the consent interface, a "denied" status will disable personalized advertisement functionalities, including remarketing and interest-based targeting. ## Pre-Requisites Before we delve into the details of the implementation, it is important that you have a published Google Tag Manager container as well as a CookieYes account. If you have Consent Mode active, that's even better. If you not, you can refer to this [implementation guide](implementing-google-consent-mode-using-cookieyes-integration) to start your implementation. ## Implementing Consent Mode V2 with Google Tag Manger CMP Template The easiest and most straightforward way to implement Consent Mode V2 is using the Google Tag Manager template offered through the integration between Google Tag Manager and CookieYes. To use this method however, you need to implement Consent Mode through the template. This will not work if you have implemented consent mode through the custom script that is gtag.js or through Google Tag Manager outside of the template. If you want to implement the consent mode through the Google Tag Manager template, here's the [official documentation](https://www.cookieyes.com/documentation/features/integrations/implementing-google-consent-mode-using-cookieyes/#Method_1_I_5) to get you started. Once your have configured Consent Mode through the template, all you have to do is to update the CMP template to use the two newly added parameters ad_personalization and ad_user_data. After you have updated the template, you can publish your changes. With these changes live, you have successfully implemented the Consent Mode V2 using Cookieyes's CMP template in Google Tag Manager. Please do not forget to test your implementation. Also make sure to follow the instructions in the official documentation to make sure you have configured the template correctly before updating to use Consent Mode V2. ## Implementing Consent Mode V2 using Google Tag Manager outside the CPM template If you followed our tutorial on how to implement Consent Mode using Google Tag Manager, you will notice that our implementation does not rely on the CMP template. The way consent was implemented using our methodology relies on: 1. Cookieyes's Cookie (stores user's current consent) 2. Consent Mode (Google tags) 3. Custom JS variables which return the user's consent mode 4. Cookieyes' GCM setting Let's get into how you can update your set up to implement Consent Mode V2. First, log into your workspace and update the Consent Mode (Google tags) tag. In this updated version of the tag, you see the two new parameters ad_personalization and ad_user_data. When you do update the tag, all consent settings will be set to denied. What you need to do is change the configuration so that each consent setting is mapped to the right custom JS variable that captures the user's consent choice for that category. In our linked tutorial, we do walk you through how you can create custom JavaScript variables that capture the user's current consent. For the two new parameters, map them to reflect the Advertising consent choice since they are indeed tied to advertising. Once you have updated your the tag and mapped out the parameters, it's time to test the new implementation. Preview your workspace, and check the consent status of the different consent settings on the Consent call in your Debug window. The next step is to verify that our tags did not and did, indeed, respect our consent settings. After confirming that our tags are respecting the current consent settings, it is time to update the settings. This time will accept all cookies except Advertising ones to ensure that the new Consent Mode V2 parameters are mapped correctly to that setting. If your implementation is correct, only 3 consent types are still denied: 1. ad_storage 2. ad_user_data 3. ad_personalization You can check to see if any tags related to analytics storage are now firing properly. After you are done confirming, it's time to update the ad_storage and see if everything works as expected. Head to your consent banner and accept advertising cookies. One final thing to confirm prior to publishing your container is if advertising tags are firing as expected. When you are done checking and are sure everything looks good, proceed to publishing your container. Congratulations, you have successfully implemented Google Consent Mode V2 using Google Tag Manager without using the CMP template. ## Conclusion Google Consent Mode V2 is essential for advertisers in the EU/EAA/UK to maintain compliance with privacy laws and uphold user trust through transparent data practices. By implementing this advanced consent management tool, businesses can ensure their advertising strategies are both effective and privacy-compliant. The transition to Consent Mode V2, while technical, is a crucial step in aligning with legal standards and user expectations for data privacy. Stay proactive in adapting to these regulations to safeguard your advertising capabilities and foster a respectful relationship with your audience.

Implementing Google Consent Mode with Google Tag Manager and OneTrust

April 2, 2024

In previous posts we have discussed how you can implement [Consent Mode with Google Tag Manager and CookieYes](/implementing-google-consent-mode-using-cookieyes-integration/), as well as how you can implement [Consent Mode V2 with Google Tag Manager and CookieYes](/implementing-google-consent-mode-v2-using-cookieyes-integration/). In this tutorial, we will walk through how you can implement Google Consent Mode, including Consent Mode V2, with OneTrust and Google Tag Manager. We will discover how this can be done using two different methodologies which should give you enough options when deciding how to implement this integration. ## Implementation Prerequisites Before we delve into the implementation, you would need to have the following at your disposal: 1. OneTrust account 2. Configured banner for your geolocation rule(s) 3. Google Tag Manager account 4. Consent Mode Activated It is also recommended to have a basic understanding on JavaScript as well as the dataLayer. This is not mandatory, but it will make sure that you understand the steps in this tutorial faster. Also, keep in mind that the following tags are the following ones: - Google Ads: this includes Google Ads Conversion Tracking and Re-marketing. Support for Phone Conversions is pending. - Floodlight - Google Analytics - Conversion Linker If you need to implement Consent Mode for other tags within Google Tag Manager, further configuration is required which we will go through later on in the tutorial. **Google Consent Mode is no an alternative to script blocking.** ## Method One: Google Tag Manager + OneTrust Consent Mode Integration For this method, we will be using OneTrust's UI as well as Google Tag Manager's UI. This implementation will not be code-heavy and is the faster of the two. Please note that everything discussed here can indeed be implemented through gtag.js. A future tutorial is going to discuss how to do that , but as far as this tutorial is concerned we will not be discussing gtag.js. ## Implementing Google Consent mode with OneTrust cookie consent In this step, we will see how you can configure OneTrust's consent settings and sync them with Google's Consent Mode ensuring OneTrust's handling on the consent update command. This will come in handy as you wouldn't worry about adding custom dataLayer events to handle updates of consent which decreases tech debt. Here's how you can enable the integration between Google Tag Manager's Consent Mode and OneTrust's cookie consent: 1. On the Cookie Consent menu, select Geolocation Rules. The Geolocation Rule Groups screen appears. 2. Select a geolocation rule group from the list. The Geolocation Rule Group screen appears. 3. Select a rule to configure. 4. Click the Edit icon in the rule header to configure the rule. 5. Enable the Google Consent Mode setting.By default, the Performance category will be associated with analytics_storage and the Targeting category will be associated with ad_storage, ad_user_data, and ad_personalization. You can change categories to associate with each consent key setting and save the settings for the Geolocation Rule. **Please note that is recommended to associate the Targeting category with ad_storage, ad_user_data and ad_personalization** 6. Configure the categories 7. Once Google Consent Mode and all [other configurations](https://my.onetrust.com/s/article/UUID-1b60bb62-5260-6045-738b-09e4fa27c538) are ready, publish the domain script.For more information, see [Publishing and Implementing Cookie Consent Scripts](https://my.onetrust.com/s/article/UUID-7478d3b4-18eb-3ac0-a6fd-fb7ebff9f8dc). 8. Ensure you have implemented the gtag arguments for the default consent type settings across the domain on all pages. Remember that the default settings must be called before Google Tags or OneTrust loads. **The order of the code here is vital. If your consent code is called out of order, your consent defaults will not function as expected.** The OneTrust script will handle the update commands to the consent type settings in line with the configurations in the geolocation rules based on the user's consent. ## Google Consent Mode V2 with OneTrust's cookie consent As you may have noticed, there are two new parameters in the Google Consent Mode being ad_personalization and ad_user_data. These two new parameters are the changes introduced in Consent Mode V2. ### Google Consent Mode V2 Updates The European Commission announced that Google has been designated as a gatekeeper under the Digital Markets Act (DMA). The DMA is a new piece of legislation designed to harmonize platform regulation across the EU and ensure fair and open digital markets. To keep using measurement, ad personalization, and remarketing features, you must collect consent for use of personal data from end users based in the EEA and share consent signals with Google. To support you with collecting granular user consent, Google has updated the consent mode API to include two additional parameters: - **ad_user_data**: Sets consent for sending user data related to advertising to Google. - **ad_personalization**: Sets consent for personalized advertising. See Implementing Google Consent Mode with OneTrust Cookie below to see how you can ensure the V2 updates are added to your OneTrust implementation. Under the Digital Markets Act (DMA), Google has been designated as a gatekeeper by the European Commission. The Digital Markets Act (DMA) is a recent legislative initiative aimed at standardizing platform regulation throughout the European Union (EU) to promote fairness and openness in digital markets. To continue utilizing features like measurement, ad personalization, and remarketing, it's necessary to obtain consent for the use of personal data from end users located in the European Economic Area (EEA) and to communicate these consent signals to Google. To ensure that you can communicate these consent signals with them, Google has updated their consent API and introduced two new parameters **ad_user_data** and **ad_personalization**. **You must implement the 202311.1.0 script version or newer to support the new parameters.** With the mapping of the new parameters, your implementation is now compliant with Google Consent Mode V2. ### Implementing consent defaults with OneTrust's CMP-GCM Template It's time to move to Google Tag Manager. To add OneTrust's tag, navigate to your workspace, click 'Add a new Tag', and when prompted to choose the tag type, click on Discover more tag types in the Community Template Gallery, the search for OneTrust CMP. Once you've found the tag add it to you workspace and then it's time for some configuration. Using this template, we can ensure that we set default consent for the different GCM categories and we can even customize the default settings per region if necessary. If you are introducing defaults per region, ensure that the country names follow ISO-3166 standards. The template will also allow you to link your OneTrust Category IDs with the Google consent mode storage types, enabling the default settings and updates of consent mode to automatically adjust according to user consent. For the trigger, you can fire the tag on Consent Initialization. If you choose to fire the tag on a different trigger, please ensure that it needs to be fire prior to Google Tags and OneTrust loading, otherwise you risk non-compliance. ## Testing Google Consent Mode Before we test if the implementation is working correctly, please ensure that you have published your OneTrust script so that everything is up to date. Next, publish you Google Tag Manager workspace. To make sure everything is working as expected, we will be using the dataLayer command in the console. We will be looking for the consent event before and after the user interacted with the banner to give or revoke consent. Open the console, and type in dataLayer prior to interacting the banner and give consent. You should see something similar to this: ```json [ { "gtm.start": 1712007295339, "event": "gtm.js" }, { "0": "set", "1": "developer_id.dYWJhMj", "2": true }, { "0": "consent", "1": "default", "2": { "ad_storage": "denied", "analytics_storage": "denied", "functionality_storage": "denied", "security_storage": "denied", "ad_user_data": "denied", "ad_personalization": "denied", "wait_for_update": 500 } } ] ``` This indicates that our consent defaults have been successfully implemented. The next step is to interact with the banner and, say, accept all cookie categories. If we do so, your dataLayer should show something similar to this: ```json { "0": "consent", "1": "update", "2": { "ad_storage": "granted", "analytics_storage": "granted", "functionality_storage": "granted", "security_storage": "granted", "ad_user_data": "granted", "ad_personalization": "granted", "region": ["CA-QC"] } } ``` If you were able to see these two events in the dataLayer, or Google Tag Manager, if you are using the preview mode then your implementation is correct. You can also test further by refusing consent categories and ensuring the appropriate tags are not firing. If everything is working as expected, then congratulations you have successfully implemented Google Consent Mode (including the V2 udpate). If your tags are not respecting user consent, please ensure that consent was configured properly in Google Tag Manager. ### Basic Consent Mode vs Advanced Consent Mode When implementing the consent mode, it is important to know that there are two modes to the consent mode, basic and advanced. These two differ in their outcome and so is necessary to know which ones you want to implement. Here's a breakdown of both mode: #### Basic Consent Mode In the basic version of consent mode, Google tags are blocked from loading until the user engages with a consent banner. Before this interaction, no data is sent to Google. Once the user gives their consent, Google tags are activated and execute the consent mode APIs, sending consent states to Google in this sequence: 1. Transmit default consent states. 2. Update and send the consent states as necessary. If the user does not provide consent, no data, including the consent status, is shared with Google; the Google tags remain inactive. In such cases, Ads' conversion modeling relies on a generic model. #### Advanced Consent Mode In the advanced version of consent mode, Google tags are initially loaded as soon as the website or app is accessed. These tags initiate the consent mode API and perform the following actions: 1. Establish default consent states, which are set to deny unless modified. 2. Send cookieless signals while consent is not granted. 3. Await user interaction with the consent banner to modify the consent states accordingly. 4. Transmit comprehensive measurement data only after consent for data collection is confirmed by the user. This approach offers a more refined modeling process than the basic version, providing a model tailored to the advertiser instead of a generalized one. ## Overview | Feature | Basic consent mode | Advanced consent mode | | ----------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Tag loading | Blocked until user interaction with a consent banner. | Loads with defaults set to denied, unless configured otherwise. | | Data transmission | No data is sent before a user consents - not even the default consent status. | When consent is denied, consent state and cookieless pings are sent. When consent is granted, cookies are written and all measurement data is sent. | | Consent states | Set after user interaction. | Defaults set to denied, unless configured otherwise; updates based on user choice. | | Tag behavior after user interaction | Loads and executes consent mode APIs only when a user grants consent. | Adjusts tag behavior based on user consent choice. | | Conversion modeling | General model (less detailed modeling). | Advertiser-specific model (more detailed modeling). | ## Method Two: Google Tag Manager + OneTrustActiveGroups + Google Consent Tag Now that we have covered the implementation with OneTrust's integration. It is time to cover the alternate method of implementing Consent Mode and Consent Mode V2 with OneTrust. For this method, we will be using a dataLayer Object from OneTrust, and the Google consent mode for setting the default and updating the consent status based on the user preferences. Please note that you will need to have OneTrust implemented outside of Google Tag Manager for this to work. If you are not sure how to do this, you can follow this OneTrust [implementation guide](https://handbook.gitlab.com/handbook/marketing/digital-experience/onetrust-cookie-consent/) to get everything sorted before continuing. ### OneTrustActiveGroups Definition The first step of this implementation is to capture the OneTrustActiveGroups dataLayer variable. This is the variable we are going to use to determine the users' current consent status. There is a way to access this variable from a different window object which will briefly talk about later on in the article However, we will stick with the dataLayer for now. To access this variable, define a new variable in Google Tag Manager and choose Data Layer variable as the variable type. Name the variable something easy to identify such as OneTrustActiveGroups. For the variable name to OneTrustActiveGroups and then save the your variable. **Please note that the variable name needs to be OneTrustActiveGroups in order for the upcoming sections to work** The next step is to preview your workspace and ensure that you see the variable. ### Google Consent tag for defaults and updates The next step in the set up is to add the Consent Mode (Google tags) tag by Simo Ahava. To do so, follow these steps: 1. In the Tags tab, click on new 2. For the tag type, after clicking on tag configuration, click on Discover more tag types in the Community Template Gallery 3. Search for Consent Mode (Google tags) 4. Add the tag to the workspace Now, it's time to configure the Default settings. For this tutorial, we will set all the consent categories to denied. You can change that depending on your use case. Since we are configuring the default setting, choose consent command to be Default. The next step is to map the different consent categories to OneTrust's Consent IDs. If you have been reading this far, you know that in Method One, this can be done through UI dropdown fields. For this method though, it will require some JavaScript. Here's the code ready for you to create 4 custom JavaScript variables each for consent category: ```js function returnAnalyticsConsent() { var oneTrustActiveGroups = {{OneTrustActiveGroups}}; //Use the name of your data variable if(oneTrustActiveGroups.indexOf("C0002") !== -1) { console.log("granted"); return "granted"; } else { console.log("denied"); return "denied"; } } function returnNecessaryConsent() { var oneTrustActiveGroups = {{OneTrustActiveGroups}}; //Use the name of your data variable if(oneTrustActiveGroups.indexOf("C0001") !== -1) { console.log("granted"); return "granted"; } else { console.log("denied"); return "denied"; } } function returnFunctionalConsent() { var oneTrustActiveGroups = {{OneTrustActiveGroups}}; //Use the name of your data variable if(oneTrustActiveGroups.indexOf("C0003") !== -1) { console.log("granted"); return "granted"; } else { console.log("denied"); return "denied"; } } function returnTargetingConsent() { var oneTrustActiveGroups = {{OneTrustActiveGroups}}; //Use the name of your data variable if(oneTrustActiveGroups.indexOf("C0004") !== -1) { console.log("granted"); return "granted"; } else { console.log("denied"); return "denied"; } } ``` **Please use one function per custom JavaScript variable.** Now that our variables are ready, it's time to map the consent categories to the category IDs. | Consent Category | Category ID | | ----------------------- | ----------- | | ad_storage | C0004 | | analytics_storage | C0002 | | personalization_storage | C0003 | | functionality_storage | C0003 | | security_storage | C0001 | ### Google Consent Mode V2 Implementation If you have skipped Method One, Google Consent Mode V2 introduced two new parameters that are required to continue using features such as remarketing, measurement and ad personalization in the European Economic Area (EEA). The two new parameters are: - ad_personalization - ad_user_data For this mapping, we will map these parameters to the ad_storage and it is recommended that you do so. However, you can still map these parameters to another category, but is is highly not recommended. With the mapping in place, your implementation is now compliant with the Google Consent Mode V2. For the trigger, create a new trigger and select Consent Initialization - All Pages as the trigger type. The next step is to configure the Update command. To do so, create a new tag, that is a Google Consent (Google tags) tag, and switch the consent command to update. For this tag, keep the mapping the same as the default tag. The next thing to change the trigger. For this tag, we will create a Custom Event trigger, and this event is OneTrustGroupsUpdated. This is the event that OneTrust logs into the dataLayer when the user updates their consent preferences. With this trigger, we can communicate to Google the consent as it changes ensuring the avoidance of custom workarounds. ### Blocking Triggers This is an option that this configuration offers which acts a prevention methodology. Please note that this option **does not** replace Consent Mode. It is merely a method that acts an alternative if you do not want to implement consent mode and still block tracking when consent is not given. To use blocking triggers, follow these steps: 1. Select the Triggers tab from the main menu. The Triggers screen appears. 2. Press New. The Trigger Configuration screen appears. 3. Create a new trigger and name it accordingly, e.g. Block Performance Cookies. 4. Press the Trigger Configuration and set the Trigger type to Custom Event. 5. Set the Event name to .\*. This event applies to all events and will allow the exception trigger to override the event that is in the firing trigger. 6. Set the Trigger to fire on Some Custom Events. 7. Select Some Customn Events and set the condition to: **OneTrustActiveGroups** [does not match RegEx] **,C0002,** 8. Save the Trigger. 9. Repeat this process for the remaining Cookie Categories. 10. Apply the Trigger to Tags as an Exception. You can create blocking triggers for the different consent categories. To reiterate, this practice does not replace Google Consent Mode. ## Configuring the Consent Mode The next step of the implementation is the configuration of the consent mode of the individual tags. To do so, follow these steps: 1. Enable the consent mode overview in your container. In the admin section, click on container settings and then check the Enable consent overview under Additional Settings 2. In the tags tab, click on the shield icon next to the new button to configure the consent for the different tags 3. Select the tags you would want to configure the consent for and then click on the shield icon with the gear. This will open a screen showing 3 consent options: 1. Not set 2. No additional consent required. Select this option to indicate that your tag does not need to check for additional consent in order to fire. You may choose to use this option to distinguish tags that you have decided need no additional consent from tags that you have not yet reviewed. 3. Require additional consent for tag to fire. This tag will only fire if the status of all of the specified consent types is 'granted' when the tag is triggered. Please note the the No additional consent required is recommended for tags that are "aware" of user consent such as the Google tags or Google Ads. For custom HTML, for instance, do require additional consent especially if cookies are going to be dropped. ## Testing the implementation Now that everything has been configured, it is time to test the implementation. For this, we will be using the preview mode of Google Tag Manager to ensure that all the tags respect the user's consent. Start by previewing the your workspace and alternate between allowing specific categories and refusing them. When you refuse a specific categories, the tags that do depend on said category need to fire but be blocked by consent. In fact, there is a section in the screen dedicated to said tags. Another thing to check is the default and updates of the consent. Next to check for updates, look for the subsequent Consent event, and you should see the On-page Update column update with whatever the user has granted / denied. For instance, if the user accepts all categories, the On-page Update column should show Granted for all categories. The other tool you can use to ensure that the implementation is working is the Network tab of the Developer Tools. Here's how to do it: 1. Visit the website that has implemented Google Consent Mode. 2. Use the browser's Developer Tools to monitor network activity. 3. Look for the network call to Google Analytics or Google Ads. 4. Check for the gcs= parameter on URLs for consent status. The gcs parameter has the following format: gcs=G1[ad_storage][analytics_storage]. 5. Check for the correct gcs= parameter values based on the provided consent. The value for ad_storage and analytics_storage will be one of the following: | Value | Description | |---------|-----------------------------------------------------------------------------------------------| | **G100** | Consent for both ad_storage and analytics_storage is denied. | | **G110** | Consent is granted for ad_storage and denied for analytics_storage. | | **G101** | Consent is denied for ad_storage and granted for analytics_storage. | | **G111** | Consent for both ad_storage and analytics_storage is granted. | | **G1--** | The site did not require consent for ad_storage or analytics_storage. | ## Takeaways If you have followed along this far you should know be equipped with enough knowledge to implement Google Consent Mode, and Consent Mode V2 using Google Tag Manager and OneTrust. Whether you implement this through the native integration or through the dataLayer and other custom tags, your website will be fully compliant and you should be able to take advantage of the cookieless pings with the Google products and see an uplift in your metrics thanks to Google's modelling.

Integrate 6sense and Google Analytics 4

July 6, 2024

When working with anonymous website traffic, categorizing the data to establish patterns, especially to determine intent, can be quite confusing and fruitless. Another challenge with website data is its incompatibility with account-based marketing. Website data tends to be anonymized, and at best, user-focused which is far from o the groupings required for account-based marketing. One of the most common tools used for ABM is 6sense. In this tutorial, we will walkthrough how to integrate 6sense with Google Analytics 4 using Google Tag Manager and gtag.js ## Get 6sense API Token & Response Codes Before starting the integration, it is important to understand the response codes for the 6sense Company Identification API. This API (previously known as Company Details API) identifies anonymous website visitors by taking an IP address and matching it to an account. The returned data includes company firmographics, segments, and score data associated with that IP and company. When queried the API returns one of the following status codes: | Code | Definition | |--------|----------------------------------------------------------------------------------------------------------------------------------------------------| | 200 | **OK**<br>Successfully returned response. The response message varies, depending on the request method/endpoint and the requested data. | | OTHERS | **Other common HTTP response codes**<br>Please see [HTTP Response Codes](https://api.6sense.com/docs/#http-response-codes) to know more codes. | Now that we have an understanding of status codes, let's look at how we can generate the 6sense API token. A 6sense API token is an alphanumeric 40 character long key phrase randomly generated that gives you access to the APIs you purchased. 6sense recommends that customers create unique API tokens per use case or integration to help track usage. To get your API token, follow these steps: - You can go to the [API Token Management](https://abm.6sense.com/login?redirect=%2Fsettings%2Fintegration%2Fapitokenmanagement) page in the 6sense ABM Platform and find your API tokens. - Copy your API Token If you need to generate a new token, reach out to support@6sense.com ==This is what a 6sense API Token looks like: aa2e25wd1rrg6fa828fdc32ftahb3461e99f22ec== ## Integrating 6sense using Google Tag Manager Once you have your API at the ready, it is time to start coding! We need a custom script that will query the 6sense API and then log and event into the dataLayer indicating that the data was received. We will this event to set user properties, enriching the collected data. The script to query Company Identification API looks like this: ```js window.dataLayer = window.dataLayer || []; async function fetch6senseData() { try { var request = await fetch("https://epsilon.6sense.com/v3/company/details", { headers: { Authorization: "<Your Authorization Token>", }, }); if (request.ok) { var parsedRequestData = await request.json(); dataLayer.push({ event: "6sense_data_ready", company_details: parsedRequestData, }); } else { console.error("Request failed with status:", request.status); } } catch (error) { console.error("Error fetching data:", error); } } fetch6senseData(); ``` > Please note that the authorization token should include the word 'Token' in it. The next step is to create a Google Analytics 4 event to set the user properties. The first step is to create a Custom Event trigger on which our event will fire. Based on the code above, the custom event name will be 6sense_data_ready. Next, configure custom variables for all the user properties you want to set in Google Analytics 4. The piece of code above logs the return data object into the dataLayer. Here's what that object looks like: ```json { "company": { "company_match": "Match", "additional_comment": "Company name or domain match was found", "domain": "g.ucla.edu", "name": "UCLA", "region": "Northern America", "country": "United States", "state": "California", "city": "Los Angeles", "industry": "Education", "country_iso_code": "US", "address": "308 Charles E. Young Drive North A210 Fowler Building/Box 951510", "zip": "90095", "phone": "(310) 825-4321", "employee_range": "10,000+", "revenue_range": "$5B+", "employee_count": "22000", "annual_revenue": "7449000000", "is_blacklisted": false, "is_6qa": true, "geoIP_country": "United States", "geoIP_state": "California", "geoIP_city": "San Jose", "state_code": "CA", "industry_v2": [ { "industry": "Education", "subindustry": "Colleges & Universities" } ], "sic_description": "", "sic": "", "naics": "6113", "naics_description": "Colleges, Universities, and Professional Schools" }, "scores": [ { "is_6qa": true, "product": "6sense", "product_display_name": "6sense", "intent_score": 63, "buying_stage": "Consideration", "profile_score": 9, "profile_fit": "Weak" } ], "segments": { "ids": [4713, 28237, 218915, 53685], "names": [ "Net New Logo Website Engagement Last 30 Days", "6s for 6s: Keyword_Sales Intelligence", "Cvent: Researching Virtual Events", "EY - Enterprise Accounts (DemandGen)" ], "list": [ { "name": "Net New Logo Website Engagement Last 30 Days", "id": 4713 }, { "name": "6s for 6s: Keyword_Sales Intelligence", "id": 28237 }, { "name": "Cvent: Researching Virtual Events", "id": 218915 }, { "name": "EY - Enterprise Accounts (DemandGen)", "id": 53685 } ] }, "confidence": "Very High" } ``` For the sake of this tutorial, we will set five user properties: company name, industry, company country, company annual revenue, and profile data confidence. In Google Tag Manager, go to the variables menu. Under the User-Defined Variables section, click on the New button. For the variable type, select Data Layer Variable, and in the Data Layer Variable Name enter the following: - company.name to capture the company name - company.industry_v2[0].industry to capture the company industry - company.country to capture the company country - company.annual_revenue to capture the company annual revenue Make sure you create 5 dataLayer variables, each capturing one of the dimensions we are interested in. Now that we have our trigger created as well as our custom variables, it is time to create our Google Analytics 4 tag to add our user properties. To create the tag, follow these steps: 1. Head to the tags section in Google Tag Manager 2. Click the New button 3. For the tag configuration, choose Google Analytics and then Google Analytics: GA4 Event 4. In the Measurement ID field, enter your Google Analytics 4 Measurement ID 5. In the event name, enter {{Event}}. This ensures that the event name reflects the one captured based on the trigger. In our case, the event name will be 6sense_data_ready. You can also enter the event name manually. 6. Expand the User Properties dropdown section and click on Add Row. 7. In each row, enter the user property name you would like see reflected in your reporting and then enter the name of its respective dataLayer variable. For instance: 1. Property Name: company_name 2. Value: {{DLV - company_name}} 8. Repeat step 7 until you have added all of your user properties 9. For the trigger, choose the one we created above for the 6sense_data_ready custom event == Make sure that the value of your user properties reflect the variables you have created. == Name the tag you just created and then test the implementation by previewing the changes. If everything was correctly implemented, you would be able to see the event being captured along with the user properties in the preview mode in Google Analytics 4. If everything was captured correctly, do not forget to publish your workspace. ## Integrating 6sense using gtag.js This integration will rely on the gtag.js library so make sure that you are using gtag.js and not Google Tag Manager; otherwise, this will fail. ## Check if gtag.js is deployed on your website Before starting the integration work, checking if gtag.js is deployed is a must. This is easy to do. In your console, type gtag and if gtag is deployed you should see the following: `ƒ gtag(){dataLayer.push(arguments);}` If you do not see the above, please make sure to deploy the library. Once the library is deployed and the check returns positive, it is time to work on the integration. Here's the full code to integrate 6sense data into GA4 as user properties: ```js async function fetch6senseData() { try { var request = await fetch("https://epsilon.6sense.com/v3/company/details", { headers: { Authorization: "<Your API Token>", }, }); if (request.ok) { const parsedRequestData = await request.json(); return parsedRequestData?.company; } else { console.error("Request failed with status:", request.status); } } catch (error) { console.error("Error fetching data:", error); } } const log6senseDataToGA4 = (companyMetaData) => { if (typeof gtag === "undefined") { console.error("gtag is not defined"); return; } gtag("event", "6sense_data_ready", { user_properties: { company_name: companyMetaData?.name, industry: companyMetaData?.industry_v2[0].industry, country: companyMetaData?.country, annual_revenue: companyMetaData?.annual_revenue, }, }); }; async function enhanceUserProperties() { const companyData = await fetch6senseData(); console.log(companyData); log6senseDataToGA4(companyData); } window.addEventListener("load", enhanceUserProperties); ``` Let's breakdown the different functions. ### fetch6senseData() This function is responsible for fetching data from the 6sense API. ```js async function fetch6senseData() { try { const request = await fetch( "https://epsilon.6sense.com/v3/company/details", { headers: { Authorization: "Token YOUR_SECURELY_STORED_TOKEN", }, }, ); if (request.ok) { const parsedRequestData = await request.json(); return parsedRequestData?.company; } else { console.error( "Request failed with status:", request.status, request.statusText, ); } } catch (error) { console.error("Error fetching data:", error); } } ``` - Purpose: Fetches company details from the 6sense API. - Async/Await: Utilizes async and await for handling asynchronous operations. - Error Handling: Uses try...catch to handle any errors during the fetch operation. - Response Handling: Checks if the request was successful (request.ok) and logs any errors if the request fails. ## log6senseDataToGA4() This function logs the fetched data to Google Analytics 4 (GA4). ```js const log6senseDataToGA4 = (companyMetaData) => { if (typeof gtag === "undefined") { console.error("gtag is not defined"); return; } gtag("event", "6sense_data_ready", { user_properties: { company_name: companyMetaData?.name, industry: companyMetaData?.industry_v2[0]?.industry, country: companyMetaData?.country, annual_revenue: companyMetaData?.annual_revenue, }, }); }; ``` - Purpose: Sends an event to GA4 with user properties based on the 6sense company data. - Safety Check: Checks if gtag is defined using typeof gtag === 'undefined' to avoid reference errors. - Optional Chaining: Uses optional chaining (?.) to safely access nested properties of companyMetaData. ### enhanceUserProperties() This function orchestrates the fetching of 6sense data and logging it to GA4. ```js async function enhanceUserProperties() { const companyData = await fetch6senseData(); if (companyData) { console.log("6sense Company Data:", companyData); log6senseDataToGA4(companyData); } } ``` - Purpose: Fetches the company data and logs it to GA4. - Async/Await: Handles asynchronous operations by awaiting the result of fetch6senseData(). - Logging: Logs the fetched company data to the console for debugging purposes. - Conditional Check: Ensures that the companyData exists before attempting to log it to GA4. ### Event Listener This line sets up the function to run when the window finishes loading. `window.addEventListener('load', enhanceUserProperties);` - Purpose: Adds an event listener to the window object to call enhanceUserProperties when the window's load event fires. - Load Event: Ensures that the function runs only after all resources (like images and scripts) have fully loaded, ensuring that all necessary elements are available and the page is fully ready. The last step is to add the code above to your website and the data will start following into Google Analytics 4. ## Conclusion With these steps, you should be able to integrate 6sense's Identify API with Google Analytics 4, enhancing your data collection with account-based insights for advanced reporting.

Integrate Micosoft Clarity with Google Analytics 4

September 8, 2024

If you are a web analyst working on optimizing your digital assets' User Experience (UX), using behavioral analytics tools offering session recordings and heatmaps can come in handy, especially when working with UI/UX designers. In this tutorial, we will have a look at how we can integrate Google Analytics 4 and Microsoft's Clarity to get access to the playback URL of session recordings. With this integration in place, you will gain access to valuable information which will help you unlock insights on how users are using your application. ## Pre-requisites Before getting started with the integration, you will need the following at the ready: - Google Analytics 4 property - Microsoft Clarity account - Google Tag Manager account You can also use `gtag.js` for this integration, but you will need to code the cookie capturing logic; more on that later. ## Capturing the sessionID and userID cookie The first part of the integration is to capture Clarity's sessionID and userID cookie. These cookies are required to build the playback URL which we are going to be sending to Google Analytics 4. So, the first step is to fetch the value of both these cookies and store them in separate variables to be used later. ### Capturing the sessionID cookie The cookie we are looking to capture is `_clsk`. And to do so, all we have to do is to create a `First-party Cookie` variable in Google Tag Manager. Here are the steps you need to take: 1. Click on the variables tab 2. Under `User-Defined Variables`, click on `New` 3. From the list, choose `1st Party Cookie` 4. Name your variable. In our case, we will use the name `var_cSessionID` 5. In the cookie name field, paste `_clsk` 6. Check the `URI-decode cookie` 7. Save your variable ### Capturing the userID cookie For this section, the cookie we want to capture is `_clck`. Here are the steps you need to take to capture the value of this cookie: 1. Click on the variables tab 2. Under `User-Defined Variables`, click on `New` 3. From the list, choose `1st Party Cookie` 4. Name your variable. In our case, we will use the name `var_clarityUserID` 5. In the cookie name field, paste `_clck` 6. Check the `URI-decode cookie` 7. Save your variable ## Building the playback URL variable Now that both cookies are captured and saved in Google Tag Manager variables, the next step is to build the playback URL. This URL will also be saved in a Google Tag Manager variable which is going to be used as the value of an event parameter sent to Google Analytics 4. This variable will be a `custom JavaScript variable` we will create. Here are the steps: 1. Click on the variables tab 2. Under `User-Defined Variables`, click on `New` 3. From the list, choose `Custom Javascript` 4. In the value field, paste the following code snippet ```jsx function returnClarityPlaybackURL() { var projectID = '&lt;your-project-id&gt;'; // Replace this value with your actual Clarity project var rootURL = 'https://clarity.microsoft.com/player/'; if(!{{var_cSessionID}} || !{{var_clarityUserID}}) { return {{Undefined}}; } var sessionID = {{var_cSessionID}}.split("|")[0]; var cUserID = {{var_clarityUserID}}.split("|")[0]; var playbackURL = rootURL + projectID + "/" + cUserID + "/" + sessionID; return playbackURL; } ``` 5. Name your variable. In our case, we will name it `var_returnClarityPlaybackURL`. 6. Save the variable ## Google Analytics 4 Event Now that the playback URL is constructed, the next step is to send this data along with an event to Google Analytics 4. Here is what you need to do: 1. Create a new Google Analytics: GA4 Event 2. In the `Measurement ID` field, enter your Google Analytics 4 Measurement ID 3. In the `Event Name` field, enter `clarity` 4. Add an Event Parameter with the name `claritydimension` and have its value set to `var_returnClarityPlaybackURL` 5. Set the trigger so the event fires on every page 6. Name your tag and then save it ## Next Steps The next step is to test your implementation by previewing the workspace and make sure that the playback URL is properly being sent to Google Analytics 4.

Connecting Measurement Protocol Events to Client-Side Data

April 22, 2024

In this tutorial, we are going to be exploring how we can connect Measurement Protocol events to client-side data in order to build a better attribution for our conversions and have a better understanding of how everything connects together. Before we dive deeper into this tutorial, please familiarize yourself with the Measurement Protocol as it is quite crucial to understanding the whole process we are going to be describing here. We have covered the [Measurement Protocol](https://datajournal.datakyu.co/advanced-ga4-measurement-protocol-implementation/) in a previous post, so you can always start there. ## What is special about the Measurement Protocol If you have worked with the Measurement Protocol before you may have noticed the following peculiarities: - Most of your events implemented through MP get attributed to Unassigned - An increase in total users - An increase in Sessions - Create / Modify events configurations do not work These are some of the weird behaviours you may have noticed if you have MP implemented. The main reason behind these anomalies is that the Measurement Protocol is isolated from your client-side libraries be it gtag or GTM. But, wait, what does that mean? The best way to think about this is having two phones that allow you to speak to the same end user in this case the Google Analytics 4 servers, but these two phones do not carry each other's context. In other words, whatever you have said, requested, or created through one line, the GA4 servers will not be able to tie to whatever you are saying, requesting or creating through the other line. But, if you have been following so far, you might ask yourself the question why are these systems separated?. And, that's a great question. Before we get to the answer though, let's quickly review the use cases for the Measurement Protocol. The following are some **use cases** where the use of the MP may be useful: - Tie online to offline behaviour. - Measure client-side and server-side interactions. - Send events that happen outside standard user-interaction, like offline conversions. - Send events from devices and apps where automatic collection isn't available, like kiosks and watches. Are you noticing a pattern? If not, try again. The answer to our question lies in the uses cases. Still don't have the answer? Well, it's data enhancing or data enrichment. The Measurement Protocol was never intended to **replace** Google Tag Manager or gtag.js; it is merely meant to enhance the data collected from these methods. As such, no context is given to the Measurement Protocol, at least not automatically. Context will have to given manually in order for data to be stitched properly. In fact, here's how Google defines an overview of the Measurement Protocol. [![App Platorm](https://developers.google.com/static/analytics/devguides/collection/protocol/ga4/img/mp_sequence_diagram.png)](https://developers.google.com/static/analytics/devguides/collection/protocol/ga4/img/mp_sequence_diagram.png) As we can see, data sent from our servers to the collection endpoints will have to be manually sent. While event data or server data is quite evident, the client ID / App Instance ID is not. In fact, these IDs, along with other parameters, are the ones that carry the "context", allowing Google Analytics 4 to stitch the data together. ## Which parameters are required to stitch data together? Now that we have a better understanding of the challenge in front of us, let's work on solving it. But where do we start? As with every problem, we start at the beginning; quite elementary. To solve this issue, we know that we need to send identifiers, for the lack of a better term, in order for Google to stitch the data together. To confirm this, let's think about the concept of Server-to-Server tracking. ## What is server-to-server or server-side tracking? Server-side tracking is a method of collecting and processing data where data is sent from a server to a tracking server, rather than being sent directly from a user's browser. This technique reduces browser load, and provides more control over the data sent to analytics platforms. Based on this definition, we can state that this technique can be used to track user behaviour without having necessarily to comply with client-side consent settings, however misguidedly. After all, users do not know what is going on in the backend. However, this approach might not make much sense, would it? Therefore, we must ask ourselves: **Is there a way to connect client-side consent choices to Measurement Protocol events?** And the answer to that question is, hopefully to no ones's awe, yes. How? Well, it is the client_id. Let's have a look at the [Privacy Settings](https://developers.google.com/analytics/devguides/collection/protocol/ga4#privacy_settings) inside the MP documentation. It is stated that the client_id (or the app_instance_id) are used to functionally adopt user privacy settings such as "non personalized ads" and "limit ad tracking". This statement confirms not only the need for identifiers, but also gives us the name of one those identifiers. Now, the second part that we need to figure out is which other parameters can be sent in order for data to be properly stitched together. For that, we would need to go back to our list of inconsistencies mentioned above. ## Increased Sessions This is perhaps one of the most commonly observed side-effects of implementing events through the Measurement Protocol. But why does this happen? The answer to that question lies in understanding how sessions are counted in Google Analytics 4. ### How are sessions calculated in Google Analytics 4? At the start, GA4 measured sessions as the total count of the event session_start. However, this has changed to the following definition: Sessions are an estimate of the number of unique session IDs that occured on the site. But, what does this mean? Well, what would an increased number of sessions and this method of count have in common? New session IDs. While the session_startevent is a reserved one, this must mean that when we send new session IDs that Google Analytics 4 does not recognize, a new session starts. As such, we can conclude that the second identifier we need to send for proper attribution is the session_id. ## Increased Total Users This is the third most common observation made when working with events dispatched using the Measurement Protocol. While the answer to this issue has already been revealed above, we will reiterate it in this section to ensure that the problem is well understood. The issue stems from the client_id. How do you ask? Well, it's the stitching. In order for data to be fully connected, a match between what we have provided as a client_id must be matched with what has been collected prior to the ingestion of the Measurement Protocol event. When a new client_id is detected, a new user will be created. GA4 still uses cookies in order to identify users and, in this case, the cookie used is \_ga. While implementing the user_id can help with user deduplication if it is provided constantly across interactions with the website, it is recommended that you provide the client_id. If it is possible to send the client_id along with the user_id that would be ideal. ## Unassigned Attribution If you have been following the tutorial so far, the answer to this problem should come to you naturally. If you started reading from this point, the answer is an unrecognized session_id and an unrecognized user_id. This is quite evident. If we look at acquisition in Google Analytics 4, it can be defined either from a user-perspective aka First-touch attribution or from a session perspective aka Last-touch attribution. Unassigned is the source of attribution Google assigns when no other grouping applies. In the case of the data sent through the Measurement Protocol, if the session_id and the client_id, Google Analytics 4 will not have any context about the acquisition source of the user and the session and thus will attribute the source to Unassigned. The best way to avoid such attribution is to provide GA4 with identifiers that would allow it to properly categorize the data you are sending. ## How to retrieve the session_id and the user_id Now that we understand why it is important to provide identifiers to Google Analytics 4 when working with the Measurement Protocol, it is time to understand how to get them in order to send your events. There are two ways that this can be done; gtag.js and Google Tag Manager. For this tutorial, we will focus on Google Tag Manager. ### Defining \_ga and \_ga_measurementID The first step step to capturing the session_id and the client_id is to define two new variables in Google Tag Manager as **1st Party Cookie**. These variables will later be used to get the actual value of the parameters. Here's how you can define a 1st Party Cookie variable in Google Tag Manager: 1. Click on variables side tab 2. In the User-Defined section, click on New 3. In the Variable Configuration section under Page Variables, choose 1st Party Cookie 4. Name the variable, and in the Cookie Name type **\_ga** and check the URI-decode cookie 5. Save the variable Following the same steps, create a new variable but instead of _ga as a value, add \_ga_<your-measurement-id>. And so, if your measurement is G-PSW1MY7HB4, the cookie value should be \_ga_PSW1MY7HB4. Now that we have both of our variables in place, it is time to use the values in order to return the values we are looking for. The next step to make this create two JavaScript variables that would extract the value of the parameters we need. In truth, both cookies carry more information than just the session_id or the client_id. As such, we would need to use an extraction technique that would get us what we need. ## Extracting the value of the session_id and the client_id. ### session_id If we want to extract the value of the session_id from the cookie we just created, all we have to do is use RegExp to pull in the value we need. Here's the code: ```js function returnSessionId() { var ga4SessionCookie = {{Your _ga_<measurement-id> variable name}}; var regExp = /(?<=GS1\.1\.)\d+/gm var regExpMatch = ga4SessionCookie.match(regExp); return regExpMatch[0]; } ``` Using this function, we will be able to extract the session_id from the target variable we created in Google Tag Manager. To give your more context, this is what the value of the cookie would look like GS1.1.1713714404.20.1.1713714410.0.0.0. The part we need to extract from this string is 1713714404. ### client_id For this parameter, we will be using a similar function to extract the value of the client_id out of the **ga** cookie. Here's the function we are going to use: ```js function returnClientId() { var ga4ClientCookie = {{Your _ga cookie variable name}}; var regExp = /(?<=GA1\.1\.)\d+\.\d+/gm var regExpMatch = ga4ClientCookie.match(regExp); return regExpMatch[0]; } ``` ## Defining JavaScript Variable in Google Tag Manager The next step is to define two variables in Google Tag Manager so we can capture the values we need to send the event. Once you have defined the variables, it's time to send the Measurement Protocol event. Here's the code that we will be using: ```js const measurement_id = 'G-XXXXXXXXXX'; const api_secret = '<secret_value>'; fetch('https://www.google-analytics.com/mp/collect?measurement_id=' + measurement_id + '&api_secret=' + api_secret, { method: "POST", body: JSON.stringify({ "client_id": { { your - variable - returning - the - clientID - value } }, "events": [{ "name": "campaign_details", "params": { "campaign_id": "google_1234", "campaign": "Summer_fun", "source": "google", "medium": "cpc", "term": "summer+travel", "content": "logolink", "session_id": { { your - variable - returning - the - sessionID - value } }, "engagement_time_msec": "100" } }] }) }); ``` Add this code to your workspace as a Custom HTML tag. For the triggering, it is up to you how this code will execute. You can define a trigger through Google Tag Manager whenever an action is triggered by the user. This is the recommended option if you are not familiar with JavaScript to edit or manipulate the code. If you are familiar with JavaScript, then you can edit the code above so that it fires whenever an event happens using the addEventListener or any other method you need. ## Validating Events & Testing the Implementation The next step is to validate the events we are going to send and test the implementation to confirm that everything is working correctly. ## Validating Events The easiest way to validate events for the Measurement Protocol is to send your payload to the debugging endpoint. Instead of sending the events to /mp/collect, you can send your payload to /debug\_/mp/collect. Here are the details of the validation: #### Response | Key | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | | `validationMessages` | Array&lt;[ValidationMessage](https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=gtag#validation_message)&gt; | An array of validation messages. | #### ValidationMessage | Key | Type | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `fieldPath` | string | The path to the field that was invalid. | | `description` | string | A description of the error. | | `validationCode` | [ValidationCode](https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=gtag#validation_code) | A ValidationCode that corresponds to the error. | #### ValidationCode | Value | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VALUE_INVALID` | The value provided for a `fieldPath` was invalid. See [limitations](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events#limitations). | | `VALUE_REQUIRED` | A required value for a `fieldPath` was not provided. | | `NAME_INVALID` | The name provided was invalid. See [limitations](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events#limitations). | | `NAME_RESERVED` | The name provided was one of the reserved names. See [reserved names](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference#reserved_names). | | `VALUE_OUT_OF_BOUNDS` | The value provided was too large. See [limitations](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events#limitations). | | `EXCEEDED_MAX_ENTITIES` | There were too many parameters in the request. See [limitations](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events#limitations). | | `NAME_DUPLICATED` | The same name was provided more than once in the request. | The second method to validating your events is the Realtime report. If you payload is error free, you should be able to see the event inside the report which indicates that Google Analytics 4 servers successfully received the event. ## Takeaways Using this method, you can now send data using the Measurement Protocol and stitch your data to the client-side in order to give your MP events context and build more robust reports.

Swetrix - Google Analytics Replacement?

February 6, 2024

When asked about tools for web analytics, the most common response is likely Google Analytics. Whether referring to Universal Analytics or Google Analytics 4 depends on the timing of the question. This highlights that Google Analytics has been, and perhaps remains, the primary tool people think of for web analytics. While some argue that Adobe Analytics has been a market contender for a long time and offers great functionality, it's important to note its pricing. Adobe Analytics is indeed powerful, but its cost may not be feasible for everyone. Other tools like Mixpanel and Amplitude offer excellent alternatives, primarily focusing on product analytics. This is not to diminish their capability in marketing analytics; however, using them solely for this purpose might not fully leverage their potential. One tool that stands out as a notable alternative to Google Analytics is Swetrix. Before exploring why Swetrix is a compelling choice for those seeking alternatives, it's crucial to acknowledge that both Google Analytics and Swetrix have their unique merits. This guide aims to provide reasons to consider Swetrix for your web analytics needs if you're exploring options beyond Google Analytics. ## A brief history of Swetrix Swetrix was launched in August, 2021 as an alternative to Google Analytics that focuses on privacy while offering similar functionality to analytics tools. According to their [about us page](https://swetrix.com/about), their number one focus is transparency and simplicity. There also a couple of things that distinguish Swetrix, compared to competitors, the first things being open-source and the second being cookieless. Swetrix, since launch, has had 683 registered users and tracked over 3.9 million events across more than 2000 websites. Despite its being a relatively new company, Swetrix offers core analytics features while being compliant with major compliance laws such as GDPR, CCPA, HIPAA and more. ## Swetrix features ### Open Source & Cookieless One of Swetrix's main features is being open-source. This is mighty impressive as this means you can customize the tool to be what you want it to be. Another great outcome of the tool being open-source is the ability to have it on premise, that is to say to install it on your own server and managing it from there. This is relatively similar to implementing Google Analytics 4 server-side with Google Tag Manager; albeit much less costly. More importantly using open-source, software means finding bugs and making contributions to the community. That being said, an amazing side-effect of Swetrix being open-source is the ability to check what the team is working on in to know what is going to be released soon. Here's the their public [roadmap](https://github.com/orgs/Swetrix/projects/2). Swetrix is open startup, besides seeing their public roadmap for the development of the project, you can also check metrics related to their revenue, profit, costs and more. You check their metrics in their [performance page](https://swetrix.com/open). With this much information being available to the end user, we can safely state that their promise of transparency is upheld. As for being cookieless, that is also true. For the rest of the blog post, we will show examples both from Datakyu's own website which uses Swetrix as well as the Swetrix's public dashboard. You may ask how do I know that Swetrix is collecting data? There are two ways to go about doing that. The first one is to check your project's dashboard. Swetrix has ~1m latency when it comes to data processing which is bounds and leaps compared to Google Analytics 4, a topic we will come back to later. The second way we can check if the data has been sent is the Network tab. Using the Network tab, not only can we confirm that data was sent to Swetrix, but also check what data was sent. As far as these features go, Swetrix is doing an amazing job at transparency and being cookieless. ### Latency in data processing When evaluating your project's performance, it's crucial to consider the speed at which data becomes available for analysis. Swetrix boasts an impressive approximate latency of around 1 minute in data processing. This rapid turnaround is noteworthy, especially when compared to Google Analytics 4, providing near real-time reporting capabilities that can significantly benefit decision-making processes. However, it's important to approach this comparison with a nuanced perspective. Latency, in the context of web analytics, refers to the delay between data collection and its availability for analysis on the dashboard. A shorter latency means that website owners and marketers can quickly assess and respond to user interactions, making it invaluable for monitoring live campaigns or managing abrupt changes in website traffic. While Swetrix's data processing speed is relatively fast, allowing for quick insights into website performance, it's also essential to recognize the scope of its reporting capabilities. Compared to Google Analytics, Swetrix may not offer the same level of comprehensive reporting or boast an extensive user base. This isn't to diminish Swetrix's value but rather to highlight that it excels in providing core reporting functionalities with the added benefit of speed, making it a compelling choice for those who prioritize these aspects. Choosing the right web analytics tool often involves considering various trade-offs. Swetrix stands out for its privacy-centric approach, simplicity, and the quick availability of data. For businesses and webmasters who value these qualities and primarily require core analytics insights without the need for highly granular data, Swetrix presents an attractive alternative to Google Analytics 4. Therefore, we encourage potential users to carefully assess their specific needs, including their analytics requirements, privacy concerns, and the importance of accessing data swiftly. This thoughtful approach will help in making an informed decision that best aligns with your web analytics goals. ### Core Analytics One thing that Swetrix does best, it's core analytics. That is to say if you are trying to look at your traffic, have an understanding of your visitors flow based on snapshots or look at funnels, Swetrix got you covered. First and foremost, their visualization is really good, and clear. It even allows you to check multiple metrics at the same time. Moreover, their date range picker offers multiple choices such as: - Last 7 days - Last 4 weeks - Last 3 months And more. Their date range picker also allows for custom date ranges and comparison, as well. But perhaps their greatest feature for timeline visualization is the ability to predict upcoming traffic. Additionally, you can switch between a line chart and a bar chart for clearer visualization. Moreover, their traffic reporting tab contains allows you to see your live visitors which give you an understanding of how many users are currently on your site. Sounds familiar? Think Real Time... Another great feature of Swetrix is the overview metrics presented first thing when the dashboard loads as Figure 3 shows. Important metrics are showcased directly to the user whether in the traffic or performance reports. When checking the performance report, though, the metrics are a bit different, but they are still very important. The metrics shown are the following: - Frontend: a sum of such metrics as browser render time and DOM Content Load time - Backend: Time to first byte - Network: a sum of such metrics as DNS Resolution time, TLS Setup time, Connection time and Response time If you are a marketer that cares about page load speeds, Swetrix offers you said information without the need to implement custom JavaScript just like Google Analytics 4. While everything we have introduced is very impressive, the most impressive feature is yet to come. Swetrix's session reporting is nothing but short of impressive. In this report, you have an overview over your website's sessions. Upon clicking on a session, you will find a breakdown of all the pages and events that occurred in said session. Other details are also accessible: - Country - Region - City - OS Name - Device type - Browser - Locale - Referrer - Campaigns - UTM source - UTM medium - UTM campaign Lastly, the funnel reporting of Swetrix is a feature truly deserve to be checked. In essence, the funnel report allows you to build as many funnels as you need and the funnel configuration can be be based either on custom events or page views which gives you further flexibility for your reporting. The report itself presents 3 pieces of data: - Visitors - Dropoffs - Never entered the funnel ## Traffic & Session Reporting ### Traffic Reporting Swetrix's traffic reporting, while not as comprehensive as Google Analytics 4, still offers quite the view about acquisition data. ![alt text](./images/image.png) As Figure 8 showcases, the traffic reporting capabilities of Swetrix are basic but do offer everything a marketer needs to understand their acquisition. However, through basic, the traffic reports hold some good surprises. For instance, country data can be visualized as follows: ![alt text](./images/image-1.png) There is more. You can visualize data as pie chart as well: ![alt text](./images/image-2.png) Custom events can also be visualized and broken down based on event parameters if need be: ![alt text](./images/image-3.png) While this information is helpful, there are much to improve for this report. But, that's a topic for another time. Last but not least, the user flow report. This is perhaps one of the most important visualizations on Swetrix: ![alt text](./images/image-4.png) This visualization allows you to understand how users are navigating your website from one page to another. This report is equivalent to Google Analytics 4's Path Exploration report. You can also user the Reverse functionality to start your analysis from an endpoint instead. ## Filtering reports Filtering reports in Swetrix is quite easy and a further demonstration of how rich in features this tool is. Filtering reports is based on the dimensions you are able to see in the report you are in. For instance, you can filter performance reports based on: - Country - Page - Browser - Device type To filter the report, hover over the value you are interested in and click on the funnel icon. This will filter all the data you are seeing to that specific value. For instance, you can check the performance metrics for Chrome users or everyone else but Chrome users (another filtering feature). ## Configuration features So far, we have covered the reporting features that Swetrix offers, but it offers more than that. In truth Swetrix's configuration features are advanced and offers some great flexibility; flexibility that even Google Analytics 4 does not offer. ### Shareable Dashboards & Password Protecting An easy win over Google Analytics 4 is shearable dashboards. In fact, you can share you Swetrix dashboard with other or even make it public for others to see (the live demo on the site is the dashboard of Swetrix's own site). You can event protect your dashboard with a password if you need a selective few to see it. This is years ahead compared to sharing exploration reports in Google Analytics 4. ### Allowed origins This setting allows you to define a list of origins, or domains, that can use your Swetrix's project ID and the script to send data to your project. What's impressive in this case is that you can define this setting on a subdomain level. For example: cornell.edu, app.example.com, \*.gov.ua. In case you were wondering, no this does not mean that cross-domain tracking is possible. This remains to be tested. This feature is more of a security feature that guarantees your project does not collect data from unauthorized domains. ### IP Blacklisting This feature is most welcomed as it allows you to make sure that traffic from specific source is not counted in your project. As you might have guessed, this feature allows you to exclude internal traffic based on your IP. But, this is not the only interesting thing about this feature. In truth, you can use this blacklisting capability to exclude traffic coming from crawlers such as oncrawl to avoid skewing your traffic data. ## Installation & API Features When it comes to implementation, Swetrix offers an array of choices. Swetrix can be deployed using a script, added through npm, implemented server-side or even self-hosted. The choice is yours when it comes to how to add Swetrix to your project; a choice that speaks to the power of the tool. Swetrix offers, mainly, tracking of page views, and custom events which can be done quite easily. Their events API can also be used to send events to their servers, for instance when tracking a mobile/native application. The also have a Statistics API which can be used to retrieve your project's data programmatically. This can be very useful if you want to integrate this data into your own Business Intelligence projects, or further blend it with sales data for a 360 analysis. You can even self-host Swetrix using Docker and start using the data to your own servers. This ability to self-host this analytics stool combined with the fact that it is open-source can turn into a very powerful custom tool fine-tuned to serve your specific analytics needs. ## Is Swetrix a Google Analytics 4 replacement? The answer is yes, if you are a small/medium sized that need a tool with solid core analytics functionality; e-commerce may be an exception here. However, for a more mature business with complex analytics needs Swetrix can replace Google Analytics, but it would require some work. In truth, Swetrix has huge potential as an analytics tool and what it lacks in the UI, it makes up for in configuration, marketplace integration or even extensions. Perhaps one day it will be a full replacement to Google Analytics 4, but for now it is a great contender to Google Analytics 4.

The Ultimate Google Analytics 4 Migration Guide

May 22, 2023

It's 2023 and that means Google Universal Analytics is sun-setting soon; actually as soon as July 1st. So, if you are still relying on it, it is time to migrate to Google Analytics 4. Unfortunately, relying on UA is not option anymore. As of the indicated date, the tool will stop processing any hit. But what does this mean? It means that any data you are sending to UA will not be processed and the data will be lost. This is including any data being sent using analytics.js. This is a very important note to keep in mind because if you are sending data to GA4 using the Collect Universal Analytics events setting, your property will not be receiving and processing data. Another important detail to keep in mind that this also includes any third party integration that relies on analytics.js like Shopify. This being said, it's time to migrate GA4 and secure your web analytics against any data loss risks. If you are not sure where to get started or you are having trouble getting started, continue reading. We are going to outline everything you need to know to complete your migration to GA4. We are going to highlight 3 methods to migrate to GA4 being gtag.js, Google Tag Manager, and Segment. To make things easier, here are 3 resources you can use to make the migration a lot easier: - [GA4 Event Generator](https://www.datakyu.co/resources/sample-code-generator.html) - [Google Tag Manager Container Templates](https://www.datakyu.co/resources/google-tag-manager-container-tempaltes.html) ## Pre-migration checklist Before you start your migration to Google Analytics 4, there are some preliminary steps that you should take in order to ensure that you are set. Here is the list: - Check the Universal Analytics property - To understand which views to turn into audiences - To understand which filters to migrate - To understand which events to migrate Once you have completed these items, continue reading to understand how to proceed with the migration. If you are having any trouble with the items above, we recommend that you do not proceed as the migration can become confusing. The items above will help you build your Google Analytics 4 account structure which is an important part of the migration. If things get very fuzzy, here are some pointers to help you out: ### Turning views into audiences The loss of the views in the account structure is one of the biggest changes happening in Google Analytics 4. However, this does not necessarily mean that you cannot create something of equivalent purpose. For instance, if you are using views to view specific segments of users based on tracked events, location, language or specific page views, you can recreate this view but as an audience in Google Analytics 4. Briefly, audiences are the equivalent to segments in Universal Analytics. This is a great alternative since a lot of businesses relied on views. This alternative also works if you were segmenting your views based on subdomains. You can use the Hostname dimension in Google Analytics 4 to achieve the same result. Please keep in mind that not all views can be turned into audiences, if your view is using some advanced/custom filters, you may not be able to recreate it. But, you may be able to recreate your set up using GA 4’s [subproperties](https://support.google.com/analytics/answer/11525732?hl=en). > 💡 Subproperties are only available if you are using Google Analytics 360 ## Migrating using gtag.js gtag.js is the analytics library used with the Google Tag. Using gtag.js, you can use a single tag with multiple Google products without having to add multiple tags. This is the first method we are going to review and this method requires a minimal knowledge of JavaScript. In order to complete the migration, you will have to manually install the tag and also all the events you want to migrate. If you are worried about messing things up, don’t worry, all you have to do is use the GA4 event generator tool linked above and you will be fine. This list should help you with the most important considerations before starting your migration. If things are still unclear, you can use communities to unblock yourself. But, if everything above made sense to you, keep on reading to understand how to migrate your events to Google Analytics 4. > 💡 The tool will generate the event for you, but you still need to add in the appropriate way to your code base. ## Installing the Google Tag (gtag.js) The first step in the migration is to add GA4 to your website. This is the simplest step in the migration and instructions are already provided by Google Analytics upon creating a Data Stream. To install the Google Tag (gtag.js) manually, you will need to paste a specific code block immediately after the `<head>` tag on every page you want to track. Please do not add more than one Google Tag on a page; this can cause some issues with your tracking. **You will need to replace the G-XXXXXXXXXX with your actual measurement ID.** Installing gtag.js is one of the most important steps when migrating using this method. Essentially, a proper installation will ensure that everything that needs to be tracked is, in fact, tracked. An erroneous installation can cause data loss and incomplete data. It is worth mentioning that installing the tag requires access to your source code and the ability to change it. If you do not know how to change your source code or are afraid of causing an issue, please contact your developer and they will be able to take care of the next step. Since we have our code block, it is time to add it to our code base. We know that the code block needs to be added immediately after the `<head>` tag. Now that our tag is installed correctly, we want to make sure that the snippet is installed on all the pages that we want to track; otherwise, as mentioned before, data loss may occur. To ensure that the Google Tag has been installed properly, type gtag in your browser’s console. If you do not see ReferenceError: gtag is not defined as an error message, your installation is correct. If you do see that message, then go back to your code and make sure the snippet is pasted in the correct spot. After ensuring that the tag is correctly installed, the next step is to migrate your Universal Analytics events. In this step, we will be working with the events that were identified in the pre-migration step. ## Migrating Universal Analytics Events with gtag.js All right, now we are getting close to completing the migration to Google Analytics 4. Only one more step to go. Now that the Google Tag is installed, we want to use gtag.js to send events to GA4. But, how do we do that? First, we need to understand what is gtag.js? ### What is gtag.js? Simply put, gtag.js is a JavaScript framework that allows us to add the Google tag to web pages. And some frameworks contain APIs that they can use to perform certain actions. And, gtag.js has an API we can use to send events to Google Analytics 4. In truth the gtag.js’ API can be used for more than sending events, but we will get to that later. For now, we just need to understand how to use the API to send events to Google Analytics 4. #### gtag.js API Reference The Google tag (gtag.js) API consists of a single function, gtag(), with the following syntax: ```js gtag(<command>, <command parameters>); ``` Using this snippet, let’s send an event downgrade_account with the parameters plan and users_per_account: ```js gtag("event", "downgrade_account", { plan: "Premium", user_per_account: 3, }); ``` That's it! We now have our account set up properly. However, it is important to note that if we paste this event in our page, the event will be sent whenever the page is loaded. Most of the events that you will be tracking happen on a user interaction, i.e. a button click or a link click, which means our code will need to be readjusted to allow us to track such interactions. This part requires some knowledge of JavaScript, so pay closer attention. This is not to say what is coming is very difficult. On the contrary, the code used is just vanilla JS and requires only basic understanding of the language. Also, the code will be broken down so that it is clearer. In order for us to track interactions, the element(s) to be tracked need to be identified and the addEventListener method needs to be used. Here's what this could look like: ```js // Identifying the modal button const modalButton = document.querySelector("div.row button.modal-button"); // Adding an event listener to fire the event every time the button is clicked modalButton.addEventListener("click", (e) => { gtag("event", "modal_button_clicked", { cta: e.target.innerText, }); }); ``` This code block is identifying the modal button element on the page by using the document.querySelector method to select the button element with the class "modal-button" that is a child of a div element with the class "row". Once the button element is selected, an event listener is added to it using the addEventListener method. This listener is set to trigger every time the button is clicked. When the button is clicked, the gtag function is called with the parameters event, modal_button_clicked, and an object containing the property cta which is the inner text of the button element. This will send an event to Google Analytics with the event name modal_button_clicked and the value of the button's inner text as the cta property. This allows tracking how many times the modal button was clicked and with which text. If you are having trouble writing the event tracking code, you can use this [tool](https://www.datakyu.co/resources/sample-code-generator.html) to generate any custom event or default events. If the tracking has been implemented correctly, you should be able to see the event in GA4’s real-time view. Let’s take a look at another example where we will track a form submission as this is a very common case. The idea is to be able to send an event every time the form is submitted. The event should have the form ID as a parameter to help identify the form. In this demo, we will assume that the form has been submitted correctly. Here’s what the code will look like: ```js // Identifying the form to be submitted const myForm = document.querySelector("myForm"); // Adding an event listener to fire the event every time the form is submitted myForm.addEventListener("submit", (e) => { gtag("event", "submitted_form", { form_id: e.target.id, }); }); ``` 1. The first line identifies the specific form that is being targeted by selecting it with the querySelector method and storing it in the variable myForm. 2. The next line adds an event listener to the form that is identified in the previous line. The event listener is listening for the submit event, which means it will fire every time the form is submitted. 3. The callback function within the event listener uses the gtag method, which is a Google Analytics tracking tool, to record an event labeled submitted_form and also includes a form_id property that is passed the ID of the form being submitted (e.target.id). This allows you to track which form on your website is being submitted and how often. To test if you were successful in tracking, take a look at the Real-Time view and if you are able to see the event, then you are good to go. By now, you should be able to know how to send events to GA4 using gtag.js. Let's now take a look at the last step in the migration, which is configuring Google Analytics 4. Now that our events are being tracked and data is flowing in correctly, the last step of the migration is to configure the GA4 property. This step ensures that our conversions are being tracked and that all parameters sent along with the events are accessible during reporting. > Note: Only move forward with this step if you are sure that data is flowing in correctly. ## Adding Custom Definitions In this step, you will be defining the parameters sent along with events to make sure that they can be used in reporting inside of Google Analytics 4 or in Looker Studio. To do this, navigate to Admin > Custom Definitions. In this tab, you will be able to add custom dimensions or custom metrics. You can read more about about custom definitions [here](https://support.google.com/analytics/answer/10075209?hl=en). Let’s add a custom definition for our cta parameter as well as the form_id one. We will be adding custom dimensions. To create a custom dimension, click on Create custom dimensions. In the new tab, add the **Dimension name**; this is the name that will be shown in reports. Next, add a description to help you remember what the dimension represents. You can also skip this as it is an optional field. The last part is to choose a parameter or property from the list or enter the name of a parameter or property you'll collect in the future. Note that the custom dimensions we are creating are Event Scoped as the parameters are sent along events. So, to create our custom dimensions, we will enter the following information: - cta parameter - Dimension name: CTA Text - Description: Returns the cta text of the clicked buttons - Event parameter: cta - form_id - Dimension name: Form ID - Description: Returns the ID of the submitted form - Event parameter: form_id Creating User Scoped parameters follows a similar method. But, instead of providing the event parameter a **user property** will need to be entered. A user property is sent as an event parameter, the only difference is that the parameter you are sending describes the state of the user instead of sending extra context about the tracked event. The syntax to send user properties is also a bit different. Instead of the event command, we will be using the set command. Let’s have a look at how this would look like: ```js // Setting a user parameter for the account type when users login gtag("set", "user_parameters", { subscription: "Free", }); ``` ## Migrating Events with the dataLayer To do this, let's go back to our gtag.js examples where we tracked the click of a button and the submission of a form. We are going to do the same thing but with the dataLayer. Here's what the tracking code looks like right now before adding the dataLayer: ```js // Identifying the modal button const modalButton = document.querySelector("div.row button.modal-button"); // Adding an event listener to fire the event every time the button is clicked modalButton.addEventListener("click", (e) => { gtag("event", "modal_button_clicked", { cta: e.target.innerText, }); }); // Identifying the form to be submitted const myForm = document.querySelector("#myForm"); // Adding an event listener to fire the event every time the form is submitted myForm.addEventListener("submit", (e) => { gtag("event", "submitted_form", { form_id: e.target.id, }); gtag("set", "user_parameters", { subscription: "Free", }); }); ``` Here’s what we will be adding: ```js // Sending the event data of the button click to the dataLayer window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "modal_button_clicked", cta: "hello", }); // Sending the event data of the form submission to the dataLayer window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "submitted_form", form_id: "hello", subscription: "Free", }); ``` Here’s everything put together: ```js // Identifying the modal button const modalButton = document.querySelector("div.row button.modal-button"); // Adding an event listener to fire the event every time the button is clicked modalButton.addEventListener("click", (e) => { gtag("event", "modal_button_clicked", { cta: e.target.innerText, }); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "modal_button_clicked", cta: e.target.innerText, }); }); // Identifying the form to be submitted const myForm = document.querySelector("#myForm"); // Adding an event listener to fire the event every time the form is submitted myForm.addEventListener("submit", (e) => { // Preventing the form from reloading the page on submission e.preventDefault(); gtag("event", "submitted_form", { form_id: e.target.id, }); gtag("set", "user_parameters", { subscription: "Free", }); // Sending the event data of the form submission to the dataLayer window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "submitted_form", form_id: e.target.id, subscription: "Free", }); }); ``` Now, let's look into how this code works: 1. The first line of the code block is used to identify the modal button on the webpage by selecting it with the querySelector method. The button is selected by its class name "modal-button" within a div with the class name "row". 2. The next line adds an event listener to the modal button that is identified in the previous line. The event listener is listening for the "click" event, which means it will fire every time the button is clicked. 3. The callback function within the event listener pushes an object to the dataLayer, which is an array that is used to store and pass information from the website to the GTM container. This object contains the event name "modal_button_clicked" and a property "cta" that is passed the inner text of the button that was clicked. This allows you to track when the button was clicked and the text of the button. 4. In the second part of the code block, the script is identifying the form to be submitted by using the querySelector method and storing it in the variable "myForm". 5. The next lines of code push an object to the dataLayer that contains an event name "submitted_form" and properties 'form_id' and 'subscription' that is passed the id of the form that was submitted and the status of the subscription respectively. This allows you to track when the form was submitted, the ID of the form, and the subscription status. The data sent to the dataLayer can then be used to create custom tags, triggers, and variables in GTM, which can be used to track and analyze user behavior, providing valuable insights that can be used to refine marketing strategies and enhance the customer experience on the website. To verify if the implementation has been successful, we can look at the dataLayer object. After completing an event, open the browser's console and type dataLayer. If the implementation has been successful, you should see something similar to this: ```js Array(7) [ Arguments, Arguments, {…}, {…}, Arguments, Arguments, {…} ] 0: Arguments { 0: "js", 1: Date Fri Jan 13 2023 10:14:48 GMT-0500 (Eastern Standard Time), "gtm.uniqueEventId": 1, … } 1: Arguments { 0: "config", 1: "G-XXXXXXXXXX", … } 2: Object { event: "gtm.dom", "gtm.uniqueEventId": 3 } 3: Object { event: "gtm.load", "gtm.uniqueEventId": 4 } 4: Arguments { 0: "event", 1: "submitted_form", 2: {…}, … } 5: Arguments { 0: "set", 1: "user_parameters", 2: {…}, … } 6: Object { event: "submitted_form", form_id: "myForm", subscription: "Free", … } ``` You can see that the last item in our array is referring to the form submission event; Hooray! **Please note that after each event, you should be retyping dataLayer in the console.** Now that we know our events are properly logged into the dataLayer, let’s create the tags that will fire the events and send the data to our Google Analytics 4 property. The first step will be creating some variables. Let’s navigate to Google Tag Manager and from there click on Variables. In theUser-Defined Variables section, click on New. In the Variable Configuration tab, choose Data Layer Variable. In the input field, we will need to enter the name of the variable we are insterested in. For the form ID, we will enter form_id, since that is the name of the variable, we are logging into the dataLayer. For the for cta, we will enter cta. Next, give a name for each of the variables and navigate to the Tags. Once in tags section, click New, and then Choose Google Analytics: GA4 Event in the Choose tag type tab. In Tag Configuration, choose the Google Analytics 4 configuration tag created earlier for the Configuration Tag. Next, add the event name and the for the event parameters, add cta with the value referring to cta variable created and add form_id with the value referring to the form_id created earlier. Now, it’s time to configure the triggers. Since we are working with the dataLayer, all of our triggers will be based on the events we are logging into the dataLayer. The reason behind this is that it makes it easier for troubleshooting. For instance, if the we create a tag that is supposed to send an event to GA4 when there is a submission based on a dataLayer event, but we are not seeing the event in real-time the first place we will investigate is the code block logging the event into the dataLayer. We will get to troubleshooting later, but this is one of the main benefits of working with the dataLayer. That being said, let’s configure our tags to fire whenever our events are logged. Let’s navigate to Triggers, click on New, and select Custom Event under Other. In the Event name field, enter the same event name being logged to the dataLayer and leave the Use regex matching off for now. In our case, the first trigger we will create is for the form submission, so the event name will be submitted_form. The second trigger, the event name will be modal_button_clicked. That’s it. Now we are ready to test the implementation. Since, we already know that our events are being logged to the dataLayer, the focus of the test will be on the tags. To test the implementation, the preview feature will be used. Once debugging window loads, the next step will be to test the implementation by clicking on the button and submitting the form. If everything went well, the debug tab should show the tags as fired and which trigger fired them. And that’s a wrap on migrating events with the dataLayer. If you are having trouble writing the code for the events, you can checkout this [resource](https://www.datakyu.co/resources/sample-code-generator.html) where you simply configure the event and they will generate the code for you. Now, let’s have a look at the traditional way of migrating events with the Google Tag Manager. Please note that it is not required to migrate your events with the dataLayer. It is only recommended to use this method because it offers great flexibility in tag management and also in troubleshooting. ## Migrating events with the Google Tag Manager (without the dataLayer) The migration using Google Tag Manager without the dataLayer is very straightforward. This step only involves migrating the tags configurations. We will use the same triggers since we want our Google Analytics 4 data to match that of Universal Analytics. So, the first step is the know which tags are going to be migrated. Second, start by selecting a tag as to open its configuration tab. On the top right hand side, click on ⋮ and click Copy. Start by renaming the tag, and the make sure to write down any configuration such as event category or event label Once you have the details writted down, click on Tag Type and change your tag to Google Analytics: GA4 Event. Your configuration tag should be the one created earlier, enter the name and then if you Universal Analytics tag had an event label and an event category, create 2 event parameters category and label In the value fields, enter the equivalent values. Review the configuration of your tag and if everything looks good, save it. After saving your tag, head over to Google Analytics 4 to create 2 custom definitions for the event parameters category and label. This will allow you to use these dimensions in your reports. If you do not know how to create custom definitions, we discussed the topic above, so scroll up! That’s it. You have succesfully migrated your first event to Google Analytics 4 without the use of the dataLayer. Repeat this process until you are done with all of your Universal Analytics tags. To ensure the migration has been successful, use the Preview feature of Google Tag Manager and use the debug window to make sure all of the event triggers are firing both UA events and GA4 events. If everything looks good, publish your container and your job is done. Congratulations! If you are interested in how to migrate to Google Analytics 4 using Segment as the basis, and learn more about the Measurement Protocol API as well as server-side migration just scroll down or keep on reading. We will talking about all these details in the upcoming section. Before we talk about Segment and the other details, here are some recommendations for the events naming conventions as well as the benefits of working with the dataLayer. ## Events Naming Convention for Google Analytics 4 - To choose a name for your new event, follow these rules: - Event names are case sensitive. For example, my_event and My_Even are two distinct events. - Event names must start with a letter. Use only letters, numbers, and underscores. Don't use spaces. - Event names can include English and non-English words and letters. - Do not use reserved prefixes and event names ## Reserved Prefixes - \_ (underscore) - firebase\_ - ga\_ - google\_ - gtag. ## Reserved Event names - app_remove - app_store_refund - app_store_subscription_cancel - app_store_subscription_convert - app_store_subscription_renew - first_open - first_visit - in_app_purchase - session_start - user_engagement ## Benefits of Working with the dataLayer Here are some of the advantages of using the data layer in Google Tag Manager (GTM): 1. Improved data accuracy: The data layer allows for the storage and passing of information from the website to GTM in a standardized format, which improves the accuracy of the data collected. 2. Enhanced flexibility: The data layer enables marketers to collect and track any data they want, regardless of the type of tag or analytics tool being used. This allows for greater flexibility in tracking and analyzing data. 3. Simplified data management: The data layer eliminates the need for hard coding data into website tags, making it easier to manage and update data without the need for IT involvement. 4. Greater control over data collection: The data layer allows marketers to control which data is collected and when, providing greater control over data collection and analysis. 5. Enhanced data analysis: The data layer allows for the creation of custom tags, triggers, and variables in GTM, which can be used to track and analyze user behavior, providing valuable insights that can be used to refine marketing strategies. 6. Improved website performance: The data layer can be used to track website performance metrics, such as page load times and bounce rates, which can be used to improve the overall performance of the website. ## Migrating to Google Analytics 4 using Segment If you have made it so far, thanks for reading. Also, you may be using Segment and you are wondering how to migrate to Google Analytics 4 using this tool. If that’s you, keep reading. If you are just curious about Segment and Google Analytics 4 and how you can use them together, keep on reading. > 💡 Please note that this section does not explain how to install Segment or how to track and indentify users. It is considered that all the implementation has been made and is valid. In this section, the concept will be explored to enable you to work with Segment and Google Analytics 4. The first concept we will explore is server-side tracking. The second concept is the Measurement Protocol API. These concepts are key to understanding how the migration to Google Analytics 4 will happen when working with Segment. Let's dive right in. The first thing that needs to be known is the relationship between Segment and Google Analytics 4. Segment is a Customer Data Platform that helps with data standardization. Segment acts as a central repository for data collection and distribution, which allows marketers greater flexibility for data management. Google Analytics is a web analytics platform that allows for analysis of marketing and product data. The goal of using Segment as the migration base is to avoid any extra work related to data collection. The question is: how can I use this tool that is already collecting data to migrate to GA4? The answer is to add Google Analytics 4 as a destination. But what does that even mean? If you are familiar with Segment, you will know that a destination acts as a data recipient. A data recipient will receive data from Segment or from another source like Hubspot, Stripe, or any other data-generating platform. Since Segment has the data we need, we are simply going to route it to Google Analytics 4. But how do we do that? Let's find out. Now that we know how to work with gtag.js, Tag Manager, and the dataLayer, it is time to learn about the Measurement Protocol and server-side tracking. If you are in the digital marketing industry, especially since the iOS 14 update, you must have heard the term server-side tracking. It’s kind of a hot topic and also the source of a headache for many, but it’s not as complicated as it may seem. Simply put, server-tracking is the ability to send data from non-web environments to analytics tools such as Google Analytics 4 so that we can analyze it and uncover insights. The non-web environments can be servers or mobile apps. This operation usually involves sending data through HTTP Requests from one server to another which avoids worries about browser blockers or data inaccuracies. There are some limitations to this operation, but they are tool-specific. We will dive into the limitations of server-side tracking with GA4 later. But how can we send data to Google Analytics 4 through HTTP requests? This is where the Measurement Protocol gets involved. The Measurement Protocol is a feature that allows developers to send data directly to Google Analytics servers from non-web based environments, such as mobile apps or servers. This enables developers to track user interactions and collect data from devices, such as IoT (Internet of Things) devices, that are not typically tracked by web analytics tools. Developers use the Measurement Protocol to send data to Google Analytics using a simple HTTP request, similar to how web pages send data to Google Analytics. So now that the Measurement Protocol and server-side tracking are a bit clearer, let’s dive into the migration. But, before we do so, you do not have to worry about doing much coding here, Segment will actually take care of the integration and work its magic in the background, all that is required to do is the data mapping which tells Segment how to translate its already collected data and send it to Google Analytics 4. Also, some configuration is required to activate the integration. Let’s start with the configuration part. To configure the integration between Segment and Google Analytics 4, we will need a Measurement ID and a Measurement API secret key. The Measurement ID Web stream details, and the Measurement API secret key needs to be generated. To generate one, navigate to the data stream you want to generate a key for, click on said stream and then click on Measurement Protocol API secrets. After reviewing and accepting the terms, click on create. Give the API secret a nickname such as Segment integration and click create to get your key. It’s now time to navigate to Segment. Before getting started, Google Analytics 4 needs to be added as a destination. Here’s how to do that: 1. Go to the Segment web app and click on the "Catalog" button. 2. From the Destinations Catalog, search for "Google Analytics 4" and select it. 3. Click on the "Configure Google Analytics 4" button on the top-right corner of the screen. 4. Select the source that will send data to Google Analytics 4 and provide a name for the destination. 5. On the Settings tab, enter the Measurement ID and API Secret associated with your GA4 stream and click Save. Once this has been configured, it is time to work on the mappings. Segment offers pre configured mappings and also the ability to build custom mappings. > 💡 Please note that individual destination instances have support a maximum of 50 mappings. Here’s a list of the configured mappings: - Page View - Select Promotion - View Item List - Begin Checkout - Refund - Add to Cart - Add Payment Info - Add to Wishlist - View Item - Sign Up - Purchase - View Promotion - View Cart - Login - Custom Event - Remove from Cart - Search - Select Item - Generate Lead If there is an event you want to migrate that does not match any of the events above, you can always create your own mapping. To create your mapping, proceed as follows: 1. Click on New Mapping 2. Choose Custom Event 3. For the event type, choose tracking 4. Add the event name. This is going to be an event name being sent to Segment. This setting will act as the trigger which when registered tells Segment to send an event to GA4 5. In the User Properties section, add any parameters to send with the event 6. In the Event Parameters section, add any parameters to send with the event 7. Test the mapping 8. If everything looks good, click Save That's it! Using the preconfigured mappings and custom ones, you have successfully migrated to Google Analytics 4 using Segment as the basis. Before we wrap things up, there is some information that needs clarification. This migration is only recommended if marketing data is not of utmost importance. While server-side migration offers high accuracy, it does not offer any insights on acquisition and traffic mediums, particularly for GA4's server-side migration. The Measurement Protocol does not offer any mappings for UTM parameters and other browser-level data. > 💡Note: While the Measurement Protocol does not automatically map such data, it can still be sent as custom dimensions for analysis. That being said, only consider this option if what is to be analyzed is purely product usage data where traffic and user acquisition is not of high importance. If you want to have access to marketing data, please consider one of the options above, which are the Google Tag Manager or gtag.js. That's a wrap, folks. By this point, you should be able to migrate from Universal Analytics to Google Analytics 4 without any issues. If at any point you are facing any issues and you are unsure how to proceed, feel free to reach out. PS: You can refer to this [page](https://gist.github.com/AzizDhaouadi/cc61191a46ef5d1d720153ff96dc74e0) if you want the full tracking code mentioned in the article

Tracking Calendly Meeting Requests with Google Tag Manager

May 14, 2024

For multiple SaaS businesses especially in the B2B industry lead acquisition is an important pillar for marketing since it is used in the measurement of the success of the campaigns. Inbound leads, in particular, is the focus of the majority of efforts since the lead's intent is quite high. Calendly is one of the most commonly used tools for booking meetings. And, in today's tutorial we are going to see how you can track Calendly embedded calendars on your website; ## TLDR; If you are interested just in the steps for the tracking, this TLDR; version is for that. Here are the steps: 1. Implement this code in Google Tag Manager through a Custom HTML ```js function isCalendlyEvent(e) { return e.data.event && e.data.event.indexOf("calendly") === 0; } window.addEventListener("message", function (e) { if (isCalendlyEvent(e)) { window.dataLayer = window.dataLayer || []; dataLayer.push({ event: e.data.event, }); } }); ``` 2. Set the trigger to all page views. 3. Create a Google Analytics: GA4 Event Tag 4. In the Measurement ID, add your property's Measurement ID 5. For the event name, add {{Event}} 6. For the trigger, create a custom event trigger 7. In the event name input box, add **calendly.(profile_page_viewed|event_type_viewed|date_and_time_selected|event_scheduled)** 8. Check the Use regex matching checkbox 9. Save all changes 10. Publish workspace ## Tracking Code Breakdown The tracking code, as you can see, is made out of two parts. The **isCalendlyEvent(e)** function and the message event listener. If you are unfamiliar with this type of listener, [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Window/message_event) is the best resource to learn more about how this eventListener works. Calendly's integrated scheduling page communicates key events in the booking process to the parent window. By subscribing to these events, we can monitor these notifications to trigger analytics events which in our case is dataLayer events. Events are transmitted to the parent window using window.postMessage(), hence the message eventListener. ## Function isCalendlyEvent(e) This function checks if a message received from the message event listener is a Calendly-related event. - **e.data.event**: This verifies that the event data includes an event property. - **e.data.event.indexOf('calendly') === 0**: This checks if the event string starts with the word "calendly", indicating it's a Calendly event. If both conditions are true, the function returns true. ## Event Listener window.addEventListener('message', function(e)) This part of the code listens for message events on the window object. A message event is fired when the window receives a message from another origin or iframe, in this case, typically from an embedded Calendly widget. - if (isCalendlyEvent(e)): When a message event is received, it checks if it's a Calendly event by calling the isCalendlyEvent function. - window.dataLayer = window.dataLayer || []: This line ensures that the dataLayer array exists. If window.dataLayer is not already initialized, it initializes it as an empty array. - dataLayer.push({ 'event': e.data.event }): This adds the Calendly event to the dataLayer. The dataLayer is used by Google Tag Manager to manage and deploy marketing and analytics tags on a website. The pushed object contains a single property event, which is set to the value of e.data.event, effectively recording the specific type of Calendly event that occurred. ## Reporting on Calendly's Booking Flow Now that the tag is in place and the tracking is active, you can build a funnel report in Google Analytics 4 to understand where your prospects are dropping off which you can either retarget or build similar audiences to prospects that do book a meeting. Here's what the funnel looks like: In the Add new condition , add the respective event name. So for Profile page viewed event, the event name is calendly.profile_page_viewed . Once the events are added, Apply the configuration and check how you inbound campaigns are performing. ## Conclusion With the tracking code above as well as the funnel report in Google Analytics 4, you can be sure that your inbound campaigns performance is not only tracked but can be analyzed and later optimized to ensure that you are maximizing your Marketing ROI.

Track E-commerce Events on Shopify

February 11, 2023

Shopify is one of the most common platforms for E-commerce and tracking your store with Universal Analytics is indeed very straightforward thanks to Shopify's integration. In fact all it takes is simple copy, paste of your tracking ID. However, when it comes to Googel Analytics 4, things are a bit different. Shopify announced that it will fully support Google Analytics 4 by March 2023. Not a lot of details have been revealed about this integration though. It is expected that the integration would give the same visbility as Universal Analytics i.e. purchase and the checkout process. That being said, we would have to wait until Shopify releases the integration for us to know to what extent the integration is useful. But, if you want to set up your own tracking and pass all of the parameters you require without relying much on the integration then you came to the right place. In this guide we will look at how you can track E-commerce events on Shopify. > In this tutorial, we will be using the checkout.liquid file which is only available for Shopify Plus merchants. ## E-commerce events tracking Let's dig into our tracking. Unfortunately, if you do not have a Shopify Plus account, tracking some the E-commerce events is not possible. Keep reading nonetheless. We will be tackling how to track purchases and users checking their order status pages. ### Tracking Requirements We have tackled this subject before; that is E-commerce tracking using [gtag.js](https://datajournal.datakyu.co/tracking-e-commerce-with-gtag-js/) and Google Tag Manager. For this tutorial, we will be using [Google Tag Manager](https://datajournal.datakyu.co/tracking-e-commerce-with-google-tag-manager/). The latter's dataLayer is a great tool to work with events without sending anything that is not valid. With this information in mind, there are steps that need to be done before we move to tracking our events. The steps are the following: 1. Adding the Google Tag Manager script in the theme.liquid file 2. Adding the Google Tag Manager script in the checkout.liquid file 3. Adding the Google Analytics 4 Configuration Tag ## Adding Google Tag Manager to the theme.liquid file We are going to admit that you already have a Google Tag Manager account. If that's not the case, you can follow this [guide](https://support.google.com/tagmanager/answer/6103696?hl=en) to set up your Google Tag Manager account. Once you have created your account, set up your web container and added your workspace, the next step is to add Google Tag Manager to the theme.liquid file. This step will ensure that Google Tag Manager loads on every page of your site (checkout pages excluded). To do this, follow these steps: 1. Copy the Google Tag Manager code provided in the container setup 2. In Shopify, navigate to the Online Store > Themes section 3. Find and click on the Actions button, the click on "Edit code" 4. Look for the theme.liquid file in the list of files and click on it 5. Locate the `<head>` tag in the theme.liquid and paste the first snippet immediately after the opening of the <head> tag 6. Next, locate the `<body>` tag and paste the second snippet immediately after its opening 7. Lastly, save the changes to the file ## Adding Google Tag Manager to the checkout.liquid file Adding Google Tag Manager to the checkout.liquid file is a similar process to the one we used to add Google Tag Manager to the theme.liquid file. That being said, follow the exact same steps only find **checkout.liquid** instead of theme.liquid in step 4. > We recommend that you create a backup of the original file before modifying them. ## Adding the Google Analytics 4 Configuration Tag In this step, we will add a Google Analytics 4 Configuration tag which we will be referencing later when setting up our E-commerce tag (that's right, just one). Here are the steps we need to take to create the tag: - Sign in to your Google Tag Manager account and select the container in which you want to create the tag. - Click on the "Tags" section in the container. - Click on the "New" button to create a new tag. - In the 'Tag Configuration" section, click on the "Google Analytics: GA4 Configuration" button - Give the configuration a name and paste the measurement ID of the property you want to add tracking to. The measurement ID looks like this G-XXXXXXXXXX - In the "Triggering" section, select the trigger that will fire the tag. Select the All Pages trigger. - Save the tag. ### Testing the implementation It's time to the test everything we have implemented so far. Before we preview our Google Tag Manager, make sure that you have saved the changes made to both the checkout.liquid and theme.liquid files. If everything is looking good, let's proceed with previewing the workspace. All we need to do is navigate to our Google Tag Manager workspace and click on the "Preview" button. Next, paste the URL of your store and click "Connect". A new tab will open and the test page will load. Use the Tag Assistant tab to make sure that page views are being correctly tracked. Visit some couple pages (checkout ones too) to make sure everything is being captured correctly. If you see that some pages are missing, go back to your implementation and make sure that the scripts are installed correctly. If everything looks good, it's time to move on to the fun part! > If you did not add the Google Tag Manager script to the checkout.liquid file, do not expect pageviews to show on when using GTM's preview mode. ## Non-checkout Events In our previous issue, we discussed how you can track E-commerce events regardless of the E-commerce platform you are using be it Shopify, WooCommerce, Wix... In this article, we will focus particularly on Shopify. To make things easier, we will divide our events into two categories: - checkout events - non-checkout events Non-checkout events are made of the following list: | Event | Description | | ---------------------------------- | ----------------------------------------------------- | | [add_to_cart](https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_to_cart) | A user adds items to cart | | [add_to_wishlist](https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_to_wishlist) | A user adds items to a wishlist | | [generate_lead](https://developers.google.com/analytics/devguides/collection/ga4/reference/events#generate_lead) | A user submits a form or a request for information | | [remove_from_cart](https://developers.google.com/analytics/devguides/collection/ga4/reference/events#remove_from_cart) | A user removes items from a cart | | [select_item](https://developers.google.com/analytics/devguides/collection/ga4/reference/events#select_item) | A user selects an item from a list | | [view_cart](https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_cart) | A user views their cart | | [view_item](https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_item) | A user views an item | | [view_item_list](https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_item_list) | A user sees a list of items/offerings | ### view_item This is the first event we are going to look at for the non-checkout events. As per the table above, we will be firing the event whenever the user views an item. In our case, we will fire the event every time the product details page is visited. To do this, need to find the product.liquid file (the name may slightly differ based on the theme), and we will add a script tag that will log our event to the dataLayer. Here's what the script will look like: ```js const ShopifyItem = ShopifyAnalytics.meta.product; const viewItem = () => { window.dataLayer = window.dataLayer || []; dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. dataLayer.push({ event: "view_item", ecommerce: { currency: ShopifyAnalytics.meta.currency, value: ShopifyAnalytics.meta.product.variants[0].price / 100, items: [ { item_id: ShopifyItem.variants[0].id, item_name: ShopifyItem.variants[0].name, item_brand: ShopifyItem.vendor, price: ShopifyItem.price / 100, quantity: 1, }, ], }, }); }; window.addEventListener("load", viewItem); ``` The code starts by declaring two constants ShopifyItem and ShopifyAnalytics. ShopifyItem is assigned the value of ShopifyAnalytics.meta.product, which refers to the product information for the current page being viewed. ShopifyAnalytics is an object that contains information about the Shopify store and its products. Next, the code defines a function named viewItem that pushes information about the viewed product to the data layer. The function starts by setting window.dataLayer = window.dataLayer || [], which creates an array named dataLayer if it doesn't already exist. This array will store the data that will be pushed to the data layer. The function then calls dataLayer.push({ ecommerce: null }); which clears any previous ecommerce object in the data layer. This step is optional, but it is good practice to clear the data layer before pushing new data. After clearing the data layer, the function pushes a new object to the data layer with information about the viewed product. The object has the following structure: ```json { event: 'view_item', ecommerce: { currency: ShopifyAnalytics.meta.currency, value: ShopifyAnalytics.meta.product.variants[0].price / 100, items: [ { item_id: ShopifyItem.variants[0].id, item_name: ShopifyItem.variants[0].name, item_brand: ShopifyItem.vendor, price: ShopifyItem.price / 100, quantity: 1 } ] } } ``` Finally, the code sets a window event listener that listens to the "load" event of the page. When the page has finished loading, the viewItem function is automatically invoked. > Don't forget to wrap this code block inside a script tag before adding it to you product.liquid file. ### select_item The next event on the list is the select_item event. This event is supposed to fire whenever a use selects an item. The event is supposed to fire on the following pages: - Home page - Catalog page (/collections/all) - Individual collections page Let's look at an example where we will fire this event on the /collections/all page: ```js const productItems = document.querySelectorAll( "div#ProductGridContainer li.grid__item", ); const ShopifyItems = ShopifyAnalytics.meta.products; let ShopifyItemsArray = []; const prepareShopifyItemsArray = () => { window.dataLayer = window.dataLayer || []; ShopifyItems.forEach((ShopifyItem) => { ShopifyItemsArray.push({ item_id: ShopifyItem.variants[0].id, item_name: ShopifyItem.variants[0].name, item_brand: ShopifyItem.vendor, price: ShopifyItem.variants[0].price / 100, }); }); }; window.onload = () => { prepareShopifyItemsArray(); }; productItems.forEach((productItem, productIndex) => { productItem.addEventListener("click", () => { dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. dataLayer.push({ event: "select_item", ecommerce: { item_list_id: "catalog", item_list_name: "Catalog Page", items: ShopifyItemsArray[productIndex], }, }); }); }); ``` The code starts by declaring a constant variable productItems which is assigned the value of the result of a query selector that selects all the product items on the catalog page. It then declares a constant variable ShopifyItems which is assigned the value of ShopifyAnalytics.meta.products. This is an array of objects representing all the products available on the store. The code then creates a function prepareShopifyItemsArray that pushes information about the products to an array ShopifyItemsArray. The information includes the variant ID, name, vendor, and price of the products. This function is invoked when the page has finished loading, which is determined by the window.onload event. The code then loops through each product item on the catalog page and adds an event listener to each that listens to a 'click' event. When a product is clicked, the code pushes the selected product information to the data layer. The data includes the item_list_id, item_list_name, and items properties. The item_list_id and item_list_name properties are set to "catalog" and "Catalog Page" respectively, which represent the current page and the purpose of the tracking event. The items property is set to the selected product information, which is obtained from the ShopifyItemsArray at the index of the clicked product. This code should be pasted on the /collections/all equivalent liquid file. If you are using the Dawn theme, the file you are looking for is main-collection-product-grid.liquid. After implementing the code on that page, you can modify the code block for the home page as well as the individual collection pages. Please note that when you are adding this code to the "home page", you will not find a liquid file for the home page. Instead, the home page is made of "components" such as featured collection or featured product and that's where the code needs to be added. > Make sure ShopifyAnalytics.meta.products is accessible in the page you are trying to track Let's have a look at an example where we are going to track a featured collection on the home page. Featured collections are one of the most used components in a Shopify theme, so this example should helop you understand how to track other components. First, we will begin by identifying the liquid file for a featured collection. For the Dawn theme, that's featured-collection.liquid. The next step is to look at the composition of the blocks to understand which CSS selectors to use. After that has been done, it's time to write the JavaScript code block allowing us to track the select_item event. ```js const regexNum = "[+-]?([0-9]*[.|,])?[0-9]+"; //Regular Expression to extract numbers from a string // Getting the list of related products const featuredCollection = document.querySelectorAll( "ul.product-grid.contains-card--product li.grid__item", ); // itemArray let itemArray = []; // We will use this array to sedn the data into the items array inside the dataLayer event // selectItem function const selectItem = (e) => { console.log(e.target.closest("div.card-wrapper.product-card-wrapper")); console.log( e.target .closest("div.card-wrapper.product-card-wrapper") .parentElement.querySelector( "div.card-wrapper.product-card-wrapper div.card.card--standard.card--text > div.card__content div.card__information span.price-item.price-item--regular", ) .textContent.trim(), ); itemArray.push({ item_name: e.target .closest("div.card__inner") .querySelector("div.card__content div.card__information h3 a") .textContent.trim(), price: (function () { let stringFeaturedProductPrice = e.target .closest("div.card-wrapper.product-card-wrapper") .parentElement.querySelector( "div.card-wrapper.product-card-wrapper div.card.card--standard.card--text > div.card__content div.card__information span.price-item.price-item--regular", ) .textContent.trim(); // Getting the price as a string let numbersOnlyStringFeaturedProductPrice = stringFeaturedProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let FeaturedProductPrice = Number(numbersOnlyStringFeaturedProductPrice); // Changing the price's data type into a number return FeaturedProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), }); // Defining the dataLayer window.dataLayer = window.dataLayer || []; window.dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. window.dataLayer.push({ event: "select_item", ecommerce: { item_list_id: "featured_collection_products", item_list_name: "Featured Collection Products", items: itemArray, }, }); itemArray = []; }; featuredCollection.forEach((featuredCollectionProduct) => { featuredCollectionProduct.addEventListener("click", selectItem); }); ``` Let' see what this code block does. First, it creates a regular expression pattern to extract numbers from a string. Then, it selects the list of related products using JavaScript. A selectItem function is then defined, which will be fired every time a product is selected from the list. This function uses the event target to get the name and price of the selected product and stores it in the itemArray. After that, the dataLayer is defined and the previous ecommerce object is cleared. A new ecommerce event is then pushed into the dataLayer, including information such as the event type, the ID and name of the product list, and the items in the itemArray. Finally, the selectItem function is attached to each product in the featured collection list as an event listener, so that every time a product is clicked on, the function will be fired and the ecommerce data will be pushed into the dataLayer. ### view_cart This is a new event introduced to the world of E-commerce tracking in Google Analytics 4. The event is very useful and can be leveraged in multiple ways. For instance this event can be used in a 2-step funnel along with the begin_checkout event to determine a benchmark for cart abandonment. With the right parameters, you can even do a dropoff breakdown based on cart value. In our example, we will take a look at how we can fire the event based on the page view, specifically the /cart page. The code we are going to write is going to be added to the main cart page; this page is **main-cart-items.liquid** if you are using the Dawn theme. Let's dig in. ```js const cartProducts = document.querySelectorAll("table.cart-items tr.cart-item"); let cartProductsArray = []; let cartValue = 0; const calculateCartValue = (cartItemsArray) => { if (!cartItemsArray) { return 0; } else { cartItemsArray.forEach((cartItem) => { cartValue += cartItem.price * cartItem.quantity; }); return cartValue; } }; const viewCart = () => { cartProducts.forEach((cartProduct) => { cartProductsArray.push({ item_name: cartProduct.querySelector("td.cart-item__details a") .textContent, price: (function () { let stringCartProductPrice = cartProduct .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), quantity: cartProduct.querySelector("td.cart-item__quantity input").value, }); }); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ ecommerce: null }); dataLayer.push({ event: "view_cart", currency: ShopifyAnalytics.meta.currency, value: calculateCartValue(cartProductsArray), items: cartProductsArray, }); }; window.addEventListener("load", viewCart); ``` The purpose of the code is to send data to Google Tag Manager every time the cart page is loaded, and the data sent includes information about the products in the cart, the total value of the cart, and the currency used in the e-commerce website. The code first selects all the rows in the cart table that contain product information using the querySelectorAll method and stores them in the "cartProducts" constant. An array called "cartProductsArray" is then created to store information about each product in the cart. A function called "calculateCartValue" is defined to calculate the total value of the cart. The viewCart function is then defined, which iterates over each product in the cart and pushes its information (product name, price, and quantity) into the "cartProductsArray". The function then pushes the information about the cart into the dataLayer array provided by Google Tag Manager. The event type is set to "view_cart" and the data sent includes the currency used in the e-commerce website, the total value of the cart, and the items in the cart. Finally, the viewCart function is called when the "load" event is triggered, meaning that the function will be executed every time the cart page is loaded. ### add_to_cart Since we are talking about viewing products in the cart, let's learn how to track products being added to the cart. To make things even more interesting, we will learn how to track the event when a product is being added to cart through the add to cart button and using the item quantity selector on the /cart page. That being said, we will add the first section of the tracking to the product.liquid page and the second section to the cart.liquid page. Let's dig in. ```js const addToCartButton = document.querySelector("button.product-form__submit"); const addToCart = () => { window.dataLayer = window.dataLayer || []; dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. dataLayer.push({ event: "add_to_cart", ecommerce: { currency: ShopifyAnalytics.meta.currency, value: (ShopifyItem.variants[0].price / 100) * document.querySelector("input.quantity__input").value, items: [ { item_id: ShopifyItem.variants[0].id, item_name: ShopifyItem.variants[0].name, item_brand: ShopifyItem.vendor, price: ShopifyItem.variants[0].price / 100, quantity: document.querySelector("input.quantity__input").value, }, ], }, }); }; addToCartButton.addEventListener("click", addToCart); ``` This code block is setting up an event listener for a button on a webpage, specifically the "Add to Cart" button. When the button is clicked, a function called "addToCart" is executed. The first step in the addToCart function is to reset the "dataLayer" variable. Next, the function pushes some data to the dataLayer variable. This data includes information about the event that just occurred, which is "add_to_cart". Additionally, there is information about the ecommerce transaction that just took place. This information includes the currency used (such as USD or EUR), the total value of the transaction, and details about the item that was added to the cart. These details include the item's ID, name, brand, price, and quantity. Finally, the addToCartButton has an event listener attached to it, so that when it is clicked, the addToCart function will be executed and this data will be pushed to the dataLayer. Let's now have a look at the add_cart_event using the quantity selector on the /cart page. This event should be easier to handle since we will be working with less variables. Let's dig in. ```js function updateAddToCart(event) { event.preventDefault(); // const previousValue = this.input.value; // let newValue; if (event.target.name === "plus") { console.log("clicked!"); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ ecommerce: null }); dataLayer.push({ event: "add_to_cart", ecommerce: { currency: ShopifyAnalytics.meta.currency, value: (function () { let cartItemContainer = event.target.closest("tr.cart-item"); let stringCartProductPrice = cartItemContainer .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), items: [ { item_name: event.target .closest("tr.cart-item") .querySelector("td.cart-item__details a").textContent, price: (function () { let cartItemContainer = event.target.closest("tr.cart-item"); let stringCartProductPrice = cartItemContainer .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), quantity: 1, }, ], }, }); } else { } } ``` The code updateAddToCart(event) is a JavaScript function that is triggered when an event, such as a click, occurs. The first line, event.preventDefault();, prevents the default action of the event from happening. The function then checks if the name attribute of the target element of the event is equal to "plus". If this condition is met, the function logs "clicked!" in the console and pushes data to the dataLayer array. The data that is pushed includes information about an add_to_cart event, such as the currency, the value of the item being added to the cart, and information about the item being added, such as its name, price, and quantity. If the name attribute of the target element is not equal to "plus", the function does not perform any action. > Use updateAddToCart(event) as the value of the onclick parameter for the + button ### remove_from_cart Now that we know how to track additions to the cart, let's have a look at how we can track removals from the cart. There two scenarios where a user is removing items from their cart. The scenario is when they are on the cart page and they are reducing quantities of products. And the second scenario is when they remove a product as a whole. Let's take a look at how we can track the first scenario. Since in our previous event, we added a function that listenes for event add firest the add to cart event if the event target's name is plus, we follow a similar logic but instead of the event's target being plus, we will use minus. Let's dig into the code. ```js else if (event.target.name === 'minus') { console.log('minus button clicked!'); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ecommerce: null}); dataLayer.push({ event: 'remove_from_cart', ecommerce: { currency: ShopifyAnalytics.meta.currency, value: function(){ let cartItemContainer = event.target.closest('tr.cart-item'); let stringCartProductPrice = cartItemContainer.querySelector('td.cart-item__details div.product-option').textContent.trim() let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called }(), items: [{ item_name: event.target.closest('tr.cart-item').querySelector('td.cart-item__details a').textContent, price: function(){ let cartItemContainer = event.target.closest('tr.cart-item'); let stringCartProductPrice = cartItemContainer.querySelector('td.cart-item__details div.product-option').textContent.trim() let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called }(), quantity: 1 }] } }); } ``` Here's what the whole function looks like for more context: ```js function updateAddToCart(event) { event.preventDefault(); if (event.target.name === "plus") { console.log("plus button clicked!"); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ ecommerce: null }); dataLayer.push({ event: "add_to_cart", ecommerce: { currency: ShopifyAnalytics.meta.currency, value: (function () { let cartItemContainer = event.target.closest("tr.cart-item"); let stringCartProductPrice = cartItemContainer .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), items: [ { item_name: event.target .closest("tr.cart-item") .querySelector("td.cart-item__details a").textContent, price: (function () { let cartItemContainer = event.target.closest("tr.cart-item"); let stringCartProductPrice = cartItemContainer .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), quantity: 1, }, ], }, }); } else if (event.target.name === "minus") { console.log("minus button clicked!"); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ ecommerce: null }); dataLayer.push({ event: "remove_from_cart", ecommerce: { currency: ShopifyAnalytics.meta.currency, value: (function () { let cartItemContainer = event.target.closest("tr.cart-item"); let stringCartProductPrice = cartItemContainer .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), items: [ { item_name: event.target .closest("tr.cart-item") .querySelector("td.cart-item__details a").textContent, price: (function () { let cartItemContainer = event.target.closest("tr.cart-item"); let stringCartProductPrice = cartItemContainer .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), quantity: 1, }, ], }, }); } } ``` The function updateAddToCart is triggered whenever the "plus" or "minus" button on a shopping cart page is clicked. It stops the default behavior of the button. It then determines which button was clicked ("plus" or "minus") and runs the appropriate code. When the "plus" button is clicked, it logs the message "plus button clicked!" and pushes data onto a data layer, which is an array that is used to track information about the user's interaction with the website. This data layer is used to track ecommerce information and the specific data pushed to the data layer includes the event type "add_to_cart", the currency of the website, the value of the item being added to the cart, and information about the item itself (name, price, quantity). When the "minus" button is clicked, it logs the message "minus button clicked!" and pushes data onto the same data layer, but this time the event type is "remove_from_cart". The same information about currency, value, and item details is included. The value and the price of the item being added or removed are determined by extracting a numerical value from a string of text on the page that represents the price of the item. This extracted value is then converted into a number data type and returned as the value and price. > Use updateAddToCart(event) as the value of the onclick parameter for the - button If you are wondering why the "plus" and "minus" keep coming up and you are confused about what it means, this should clarify things: ```html <button class="quantity__button no-js-hidden" name="plus" type="button" onclick="updateAddToCart(event)" > <button class="quantity__button no-js-hidden" name="minus" type="button" onclick="updateAddToCart(event)" ></button> </button> ``` Let's look at the second scenario where the user would use the "delete" button to remove the whole product from the cart. Let's dig in. ```js const removeFromCartButtons = document.querySelectorAll( "div.cart-item__quantity-wrapper cart-remove-button", ); const removeFromCart = (e) => { console.log("remove from cart button has been clicked!"); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ ecommerce: null }); dataLayer.push({ event: "remove_from_cart", ecommerce: { currency: ShopifyAnalytics.meta.currency, value: (function () { let cartItemContainer = e.target.closest("tr.cart-item"); let stringCartProductPrice = cartItemContainer .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number let removedProductFromCartValue = cartProductPrice * e.target .closest("tr.cart-item") .querySelector("input.quantity__input").value; return removedProductFromCartValue; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), items: [ { item_name: event.target .closest("tr.cart-item") .querySelector("td.cart-item__details a").textContent, price: (function () { let cartItemContainer = e.target.closest("tr.cart-item"); let stringCartProductPrice = cartItemContainer .querySelector("td.cart-item__details div.product-option") .textContent.trim(); let numbersOnlyStringCartProductPrice = stringCartProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let cartProductPrice = Number(numbersOnlyStringCartProductPrice); // Changing the price's data type into a number return cartProductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), quantity: e.target .closest("tr.cart-item") .querySelector("input.quantity__input").value, }, ], }, }); }; removeFromCartButtons.forEach((removeFromCartButton) => { removeFromCartButton.addEventListener("click", removeFromCart); }); ``` The code adds an event listener to each element in the removeFromCartButtons node list which triggers the removeFromCart function when clicked. The function logs a message, initializes an array dataLayer if it doesn't already exist, and pushes an object to the array with ecommerce and event keys. The ecommerce object contains the currency and value of the product being removed from the cart, as well as an array of items with details about the product being removed (name, price, and quantity). The product price and value being removed from the cart are determined by extracting the price from the HTML and calculating the value based on the price and quantity. The data pushed to dataLayer is used by analytics platforms such as Google Tag Manager. ### generate_lead This is the only event that is not proper to E-commerce but is very useful to online stores. If you are an online brand, and you are trying to understand the effectiveness of your newsletter signup popups or other sign up methods, this event will help do that. Before we start, we will be using the MutationObserver interface to track the newsletter. This is somewhat equivalent to Google Tag Manager's Element visibility trigger. Let's dig in. ```js // Select the node that will be observed for mutations const footerNewsletterForm = document.getElementById("ContactFooter"); // Options for the observer (which mutations to observe) const config = { childList: true, subtree: true }; // Callback function to execute when mutations are observed const successfulNewsletterSignup = (mutationList, observer) => { for (const mutation of mutationList) { if (mutation.type === "childList") { window.dataLayer = window.dataLayer || []; dataLayer.push({ event: "generate_lead", newsletter_form: footerNewsletterForm.getAttribute("id"), }); } } }; // Create an observer instance linked to the callback function const observer = new MutationObserver(successfulNewsletterSignup); // Start observing the target node for configured mutations footerNewsletterForm.addEventListener("submit", () => { observer.observe(footerNewsletterForm, config); }); ``` This code sets up a MutationObserver to watch for changes to the footer newsletter form with ID "ContactFooter". The observer is set up to look for childList mutations, meaning it will detect any changes to the child nodes of the form. If a childList mutation is detected, the observer's callback function "successfulNewsletterSignup" will be executed. The callback function pushes an event to the data layer with the event name "generate_lead" and the newsletter form's id attribute as the "newsletter_form" property. The observer is attached to the form when the form is submitted and starts observing the form for mutations. We added this code to the **footer.liquid** since that's where the newsletter is located. ## view_item_list This event is quite tricky to track. The idea is to target a list (or multiple) and have the event fire whenever the list(s) enter(s) the user's view port. Not only that, but we need the dataLayer to be populated with all the information related to the products that just became visible. To do this, we are going to use the **IntersactionObserver API**. Let's dig in. ```js const regexNum = "[+-]?([0-9]*[.|,])?[0-9]+"; //Regular Expression to extract numbers from a string // creating the observer for the recommended products list let options = { threshold: 1.0, }; // creating observedProductsArray. This will be the value of the items array in the dataLayer let observedProductsArray = []; let observedProductsList = document.querySelectorAll( 'div[data-collection-type="featured_collection"] li.grid__item', ); // List of observed products const hasBeenSeen = (entries) => { if (entries) { // Firing this event only when we are sure that the intersection happened observedProductsList.forEach((observedProduct) => { observedProductsArray.push({ item_name: observedProduct .querySelector("h3.card__heading") .textContent.trim(), price: (function () { let ObservedStringProductPrice = observedProduct.querySelector( "span.price-item.price-item--regular", ).textContent; let numbersOnlyStringObservedProductPrice = ObservedStringProductPrice.match(regexNum)[0]; let observedProductPrice = Number( numbersOnlyStringObservedProductPrice, ); return observedProductPrice; })(), }); }); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "view_item_list", ecommerce: { item_list_id: entries[0].target .closest('div[data-collection-type="featured_collection"]') .getAttribute("data-collection-type"), // entries is an object item_list_name: entries[0].target .closest('div[data-collection-type="featured_collection"]') .getAttribute("data-list-name"), items: observedProductsArray, }, }); } Productsobserver.unobserve( document.querySelector('div[data-collection-type="featured_collection"]'), ); // Unobserving the element so the callback function will only get called once }; let Productsobserver = new IntersectionObserver(hasBeenSeen, options); Productsobserver.observe( document.querySelector('div[data-collection-type="featured_collection"]'), ); ``` This code creates an IntersectionObserver to monitor the visibility of a list of recommended products in the viewport, specified by the selector 'div[data-collection-type="featured_collection"] li.grid\_\_item'. The observer's callback function hasBeenSeen is called when the observer determines that the list of recommended products has become visible in the viewport. The function then creates an array observedProductsArray of objects, where each object represents a product in the list and has properties item_name (the name of the product) and price (the price of the product). The function then pushes an object to the dataLayer array with the properties: - event: "view_item_list" - ecommerce: an object with the following properties: - item_list_id: The data-collection-type attribute of the nearest parent div element to the list of recommended products. - item_list_name: The data-list-name attribute of the nearest parent div element to the list of recommended products. - `items`: the observedProductsArray created earlier. Finally, the observer is told to stop monitoring the element by calling Productsobserver.unobserve on the nearest parent div element to the list of recommended products. That's a wrap for the non-chekout events. From this point onward, we are going to be using the checkout.liquid file in oder to track the following events: | Event | Description | | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | | [begin_checkout](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#begin_checkout) | A user begins the checkout process | | [add_shipping_info](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_shipping_info) | A user adds their shipping information | | [add_payment_info](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_payment_info) | A user adds their payment information | | [purchase](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#purchase) | A user makes a purchase | ## Checkout events ### purchase Let's begin with the most important event which is the purchase event. To track this event, we can either use the Order Status page or the checkout.liquid file. Since the checkout.liquid requires a Shopify Plus subscription, we will see how we can track this event using the Order Status page. Let's dig in. ```html <script> const purchase = () => { window.dataLayer = window.dataLayer || []; dataLayer.push({ ecommerce: null }); {% if first_time_accessed %} dataLayer.push({ event: "purchase", ecommerce: { transaction_id: "{{ order.order_number }}", value: {{ total_price | times: 0.01 }}, tax: {{ tax_price | times: 0.01 }}, shipping: {{ shipping_price | times: 0.01 }}, currency: "{{ order.currency }}", items: [ {% for line_item in line_items %}{ item_id: "{{ line_item.product_id }}", item_name: "{{ line_item.title | remove: "'" | remove: '"' }}", currency: "{{ order.currency }}", price: {{ line_item.final_price | times: 0.01 }}, quantity: {{ line_item.quantity }} },{% endfor %} ] } }); {% endif %} } window.addEventListener('load', purchase); </script> ``` The code creates a new purchase function that sets up the dataLayer object if it does not already exist. It then pushes an ecommerce data layer to the dataLayer object. The data pushed contains information about the purchase, including the transaction ID, order value, tax amount, shipping cost, currency, and items purchased. This data is only pushed if the first_time_accessed variable is truthy. ### begin_checkout, add_shipping_info, and add_payment_info From this point onwards, we will need access to the checkout.liquid file. The code we are going to be using for this event and the following two will be using liquid which will allow us to dynamically get the data we need to populate our dataLayer. Let's dig in. ```js (function($) { $(document).on("page:load page:change", function() { if (Shopify.Checkout.step === 'contact_information'){ window.dataLayer = window.dataLayer || []; dataLayer.push({ ecommerce: null }); dataLayer.push({ event: 'begin_checkout', ecommerce: { transaction_id: "{{ order.order_number }}", value: {{ total_price | times: 0.01 }}, tax: {{ tax_price | times: 0.01 }}, shipping: {{ shipping_price | times: 0.01 }}, currency: "{{ order.currency }}", items: [ {% for line_item in line_items %}{ item_id: "{{ line_item.product_id }}", item_name: "{{ line_item.title | remove: "'" | remove: '"' }}", currency: "{{ order.currency }}", price: {{ line_item.final_price | times: 0.01 }}, quantity: {{ line_item.quantity }} },{% endfor %} ] } }); } else if (Shopify.Checkout.step === 'payment_method') { window.dataLayer = window.dataLayer || []; dataLayer.push({ ecommerce: null }); dataLayer.push({ event: 'add_shipping_info', ecommerce: { transaction_id: "{{ order.order_number }}", value: {{ total_price | times: 0.01 }}, tax: {{ tax_price | times: 0.01 }}, shipping: {{ shipping_price | times: 0.01 }}, currency: "{{ order.currency }}", items: [ {% for line_item in line_items %}{ item_id: "{{ line_item.product_id }}", item_name: "{{ line_item.title | remove: "'" | remove: '"' }}", currency: "{{ order.currency }}", price: {{ line_item.final_price | times: 0.01 }}, quantity: {{ line_item.quantity }} },{% endfor %} ] } }); } else if (Shopify.Checkout.step === 'processing') { window.dataLayer = window.dataLayer || []; dataLayer.push({ ecommerce: null }); dataLayer.push({ event: 'add_payment_info', ecommerce: { transaction_id: "{{ order.order_number }}", value: {{ total_price | times: 0.01 }}, tax: {{ tax_price | times: 0.01 }}, shipping: {{ shipping_price | times: 0.01 }}, currency: "{{ order.currency }}", items: [ {% for line_item in line_items %}{ item_id: "{{ line_item.product_id }}", item_name: "{{ line_item.title | remove: "'" | remove: '"' }}", currency: "{{ order.currency }}", price: {{ line_item.final_price | times: 0.01 }}, quantity: {{ line_item.quantity }} },{% endfor %} ] } }); } }); })(Checkout.$); ``` The code checks the value of Shopify.Checkout.step to determine the current step of the checkout process. Based on the step, the code pushes different events to the Google Tag Manager data layer, which is an array that is used to pass information from a website to Google Tag Manager. For each event, the code sets the event property and an ecommerce object with various properties like transaction_id, value, tax, shipping, currency, and items. The properties are set using Shopify Liquid, a template language used to access and display dynamic data in Shopify stores. Here's what happens in each of the three steps: - If Shopify.Checkout.step is contact_information, the code pushes an begin_checkout event to the data layer. - If Shopify.Checkout.step is payment_method, the code pushes an add_shipping_info event to the data layer. - If Shopify.Checkout.step is processing, the code pushes an add_payment_info event to the data layer. In all cases, the code sets the ecommerce object with the details of the transaction, such as the transaction_id, value, tax, shipping, currency, and items. The items array contains details about each line item in the order, such as the item_id, item_name, currency, price, and quantity. ### Bonus Event: viewed_order_page If you are interested in knowing if your customers are visiting your order status page after their purcahse, there is a way to do so. Before we explore this, it's important to understand how the order status page works in Shopify. The **Order Status** page is usually considered as a checkout page. However, the first time a customer visits the page, it's considered as a Thank You page, where the Shopify.Checkout.step and Shopify.Checkout.page objects are defined. If the customer revisits or reloads the page, then this checkout is converted to an order, and the page loads as an Order Status page, where the Shopify.Checkout.step and Shopify.Checkout.page objects are undefined and the Shopify.Checkout.OrderStatus object is defined. That being said, let's dig in. ```js if (Shopify.Checkout.OrderStatus) { window.dataLayer = window.dataLayer || []; dataLayer.push({ event: "viewed_order_page", }); } ``` That's a wrap on all of our E-commerce related events. So, now that we tested our code and we are sure the right information is being pushed to the dataLayer, what do we do next? ## The E-commerce Tag The next step is configuring E-commerce tags into Google Tag Manger. Unlike our tracking, we do not have to create multiple tags for each event. Instead, one tag will rule them all. Let's dig in. The first thing we need to do is create a new tag; a Google Analytics: GA4 Event tag. For the event name type in {{Event}}. This variable captures the names of the events we are logging into the dataLayer and uses those names as the event's name. Next, expand the More Settings drop down and check the box for Send Ecommerce data. Keep the dataLayer as the Data Source. Our tag is now configured. The next step is to configure our trigger. Create a new trigger of Custom Event type, and in the event name paste the following: ```plaintext view_item|view_item_list|select_item|add_to_cart|remove_from_cart|begin_checkout|add_shipping_info|add_payment_info|purchase ``` The next step would be to check the Use regex matching checkbox. This will allow the trigger to fire every time one the events in the list is detected. And you are done! As for the generate event, follow the same instruction but omit the Send Ecommerce data setting and only add generate_lead as the Event name when you are creating your trigger; there is no need to use regex for this one. That's it folks! The instructions layed out in this article should help you implement E-commerce tracking for your Shopify store and give your full visibility over your users behaviour and buyer journey.

Track E-commerce Events with Shopify's GA4 Integration

July 26, 2023

Shopify has released its integration with Google Analytics 4 making you E-commerce tracking seamless. But is it though? In today's post, we will be looking into how the integration works to see what is tracked out of the box as well as which events, if you want to track them, will have to be tracked manually through Google Tag Manager or gtag.js. Let's dive in. ## Shopify's Integration First and foremost, please go ahead and read the [documentation](https://help.shopify.com/en/manual/reports-and-analytics/google-analytics/google-analytics-setup) by Shopify in order to get better context of what we are going to be doing. Essentially, this documentation will help you understand the requirements for this endeavour. ## Disabling Existing Tracking Before you do anything else, there is one important task that needs to be done. And, that's disabling any current implementation that is sending data to Google Analytics 4. This is going to ensure that you are not sending the data twice to GA4. To do this, you must remove two things: - The installation script from you theme.liquid file - The purchase tracking from the order status page Your installation script should look something like this if you installed GA4 through gtag.js: ```html <!-- Google tag (gtag.js) --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-PSW1MY7HB4" ></script> <script> window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag("js", new Date()); gtag("config", "G-PSW1MY7HB4"); </script> ``` If you used Google Tag Manager then you would want to remove the following code snippets: ```html <!-- Google Tag Manager --> <script> (function (w, d, s, l, i) { w[l] = w[l] || []; w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" }); var f = d.getElementsByTagName(s)[0], j = d.createElement(s), dl = l != "dataLayer" ? "&l=" + l : ""; j.async = true; j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl; f.parentNode.insertBefore(j, f); })(window, document, "script", "dataLayer", "GTM-XXXXXXX"); </script> <!-- End Google Tag Manager --> ``` ```html <!-- Google Tag Manager (noscript) --> <noscript ><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX" height="0" width="0" style="display:none;visibility:hidden" ></iframe ></noscript> <!-- End Google Tag Manager (noscript) --> ``` For the purchase tracking code, follow these steps: - From your Shopify admin, go to Settings > Checkout. - In the Order status page section, go to the Additional scripts text box. - In the box, locate either the Google Tag Manager or the gtag.js code for the purchase tracking and delete them. > Make sure to create a copy of the existing code before removing any code. If you are ensure of what to remove, please contact a developer to help with this. It is important not remove code that may affect the way your website works. That being said, once the code pieces have been removed, it is time to start the integration between Google Analytics 4 and Shopify. ## Intergrating Google Analytics 4 and Shopify To integrate Shopify and GA4, all we need to do is to install the Google app for Shopify. If you have this app already installed in your shop, you can skip to the next step. If not, follow these steps: - Log into you Shopify store, and search for the Google & Youtube app in Sales Channels (or you can click on this [link](https://apps.shopify.com/google)) - Install the app by clocking on Add app - Once the app is installed, click Add sales channel - Next, connect the app using the Google account used to set up Google Analytics 4. - Auhtorize the app to access Google Analytics - Click on Get started to set up Google Analytics 4 - Connect to your Google Analytics 4 property by selecting it from the dropdown - Click connect once you have selected the right property - If you seccessful, you should see a validation message confirming that the property is set up. > If you encounter an error message, try to connect your property again. ## Validating the integration using Google Tag Manager and Debug View Once you see the validation message, the next step is to test the integration to ensure that Google Analytics 4 is receiving the events. To do so, navigate you Google Tag Assistant by following this [link](https://tagassistant.google.com/), click on Add domain, enter your store's URL and click Connect. Once the new window opens, proceed to mock a purchase. That is, select a product, add to the cart, checkout, enter the checkout information and make a purchase. If you look at the Google Tag Assistant tab, as you are firing the events, you will see the equivalent events logged into Google Analytics 4. You can also use the Debug View of Google Analytics to see the incoming events. If anything looks off, you can contact support and they will help you solve any issues. If you are ensure which events you should be looking for, here's a list: | Event name | Event description | | ---------------- | ------------------------------------------------------ | | **page_view** | A customer visited a page on your online store. | | **search** | A customer searched for a product on your online store.| | **view_item** | A customer viewed a product on your online store. | | **add_to_cart** | A customer added a product to cart. | | **begin_checkout** | A customer started the checkout process. | | **add_payment_info** | A customer successfully entered payment information. | | **purchase** | A customer completed their checkout. | ## Customizing the tracking If you want to customize the tracking in place to add additional events, you can use Google Tag Manager or gtag.js to do so. Unfortunately, the event add_shipping_info would require Shopify Plus so you can edit the checkout file. Events like view_cart, view_item_list can be tracked using the above mentioned methods. However, to do so, a minimum knowledge of JS is required if you want to use gtag.js. We have prepared a comprehensive [tutorial](./track-e-commerce-events-on-shopify/) on how you can track Shopify-related events. Check it out and feel free to reach out to us if you have any questions. That's a wrap. And, as usual, happy tracking!

Track Gravity Forms with Google Tag Manager

July 30, 2024

## Introduction Gravity Forms is one of the most widely used plugins for form creation on WordPress sites. And in this tutorial we will see how we can track these forms. Specifically, we will learn how we can track successful form submissions when the form shows a thank you message or redirects to a thank you page / third party URL. ## Tracking Form Submissions - Confirmation Message When creating a Gravity Form, showing the user a thank you message is the default behaviour. Let's learn how to track this. Gravity Forms provides us with a default list of JavaScript Hooks that we can use in order to customize the way our form looks or interacts with the user based on specific conditions. One of said hooks is gform_completion_loaded. This hooks fires on AJAX-enabled forms when the confirmation message is displayed. You can read more about this hook in the official documentation. ## What are AJAX enabled forms AJAX-enabled forms are web forms that use Asynchronous JavaScript and XML (AJAX) to submit form data to the server without requiring a full page refresh. ## Tracking code for Gravity Forms - Confirmation Message Let's look at how to track successful form submissions when our gravity forms are programmed to show a confirmation message. ```js jQuery(document).ready(function () { jQuery(document).bind("gform_confirmation_loaded", function (event) { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "successful_form_submission", }); }); }); ``` You can customize the code above to track specific submissions based on which confirmation message is shown on the users' screens. Our code then would look something like the following ```js jQuery(document).ready(function () { jQuery(document).bind("gform_confirmation_loaded", function (event, formID) { window.dataLayer = window.dataLayer || []; if (formID === 1) { window.dataLayer.push({ event: "qualified_leads_form_submitted", }); } else if (formID === 2) { window.dataLayer.push({ event: "contact_us_form_submitted", }); } }); }); ``` You can use the check on the formID to customize other tracking flows if need be. Please note that in order for the tracking script to work, jQuery needs to be deployed on your side. If you are using Wordpress to build your website, jQuery is most likely already deployed. If you are deploying any of the code blocks above on Google Tag Manager, make sure the Custom HTML tag fires on all the pages where the Gravity Forms are deployed. ## Tracking Form Submission - Thank you URL & Third Party URL The real challenge with Gravity forms is tracking form submissions when form redirects to a thank you page or a third party URL. The challenge is that Gravity forms do not offer any out-of-the-box JavaScript hooks that we can use to track successful form submissions. If you have a form that redirects to URL, using the above script will result in tacking failure. This is because the JavaScript hook above requires the form to use a confirmation message, not a URL redirect. In order to track these forms, we need a script that validates the forms, and then sends tracking data to the appropriate analytics tools. Here's the outline our script: 1. Interrupt Default Behaviour on Form Submission: Prevent the form from submitting in the traditional way. 2. Validate the Form: Check that all required fields have valid data (e.g., email is not empty). 3. If the Form Data is Valid, Send Tracking Data: Execute the tracking code to log the form submission event. 4. Resubmit the Form While Avoiding Recursion: Programmatically submit the form only if validation passes, and ensure the form doesn't enter a recursive submission loop. ## What is a recursive submission loop? Before answering this question, let's look at what recursion is. Recursion is a process where a particular set of instructions reference themselves as such creating a loop where a process calls itself until the exit condition is met. In the context of form submissions, a recursive submission loop represents a form programmatically submitted until an exit condition is met. This type of behaviour is dangerous as it could lead the form to be infinitely submitted if the checks are not set properly ## Tracking code for Gravity Forms - URL Redirect Let's breakdown a script that validates a Gravity form redirecting to URL on successful form submission and sends an event to the dataLayer. ```js jQuery(document).ready(function ($) { // Attach submit event to all Gravity Forms $(document).on("submit", ".gform_wrapper form", function (event) { // Prevent the default form submission behavior event.preventDefault(); var form = $(this); var emailField = form.find('input[type="email"]'); // Check if the form is already being submitted if (form.data("isSubmitting")) { return; } // Check if the email field is not empty and select field has a valid value if (emailField.length && emailField.val().trim() !== "") { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "succesful_form_submission", }); // Set a flag indicating the form is being submitted form.data("isSubmitting", true); // Remove the submit handler to avoid recursion $(document).off("submit", ".gform_wrapper form"); // Submit the form programmatically form.submit(); } else { console.error( "Form validation failed. Either email is empty or select value is not valid.", ); } }); }); ``` - Document Ready: Ensures the enclosed code runs only after the DOM is fully loaded. - Attach Submit Event: Attaches a submit event handler to all forms within elements that have the class gform_wrapper. - Prevent Default Behaviour: Prevents the default form submission behaviour to allow for custom processing. - Define Variables:form: Refers to the form that triggered the submit event.emailField: Finds the email input field within the form. - Check Submission Flag: Checks if the form is already being submitted. If so, exits the function to prevent multiple submissions. - Validate Fields: Checks if the email field is not empty. - Push Event to Data Layer: Sends a tracking event to the dataLayer indicating a successful form submission. - Set Submission Flag: Sets a flag (isSubmitting) on the form to indicate it is being submitted. - Remove Submit Handler: Removes the submit handler from the document to avoid recursion. - Programmatically Submit Form: Submits the form programmatically. - Validation Failure: Logs an error message if the email field is empty or the select field value is invalid. Even if your form redirects to a URL, you can still run conditional tracking based on the ID by grabbing the value during form submission. All you have to do is create a variable that captures the form ID and add an additional if statement before logging event data into the dataLayer. ## Conclusion With these tracking scripts you should be able to track all Gravity forms whether they are showing users a confirmation message or redirecting them to a URL.

Track Typeform Forms with Google Tag Manager & Google Analytics 4

March 29, 2023

Tracking user interactions with online forms is essential to optimize for your forms' submission as well as conversion rate. If you're using Typeform to create forms for your website or application, you may know that it is not that straighforward to track form submissions and dropoffs outsite of the integration Typeform has with Google Analytics 4. In this blog article, we'll show you how to track Typeform forms using Google Tag Manager (GTM) and Google Analytics 4 (GA4). With this setup, you can easily capture form events and send them to GA4 for analysis and reporting. By the end, you'll have a comprehensive understanding of how to track Typeform forms and you can this methodology to send conversiond data to Google Ads and other platforms, if needed. Let's dig right in. ## Current Tracking Challenges If you have worked with Typeform before, you would have noticed that it is not very straightforward to track form interactions in Google Analytics outside of the integrations that Typeform offers. The main reason behind this is that Typeform is embedded as an iframe. Iframes are not very friendly when it comes to tracking particularly tracking using Google Tag Manager which is commonly user especially with Google Analytics 4. That being said, outside the methods we will be showing today, tracking is still possible throught the use of the postMessage() method in JavaScript. However, this method requires quite an advanced knowledge of JS. Another challenge to this tracking method is cross-browser compatibility. All this said, you can still proceed with this method should you choose to. If you need any help generating the tracking code using the dataLayer, check out our free [Google Analytics 4 tracking code generator](https://www.datakyu.co/resources/sample-code-generator.html). ## Tracking Form Interactions ### Tracking Form Interactions for HTML Embeds There are two main ways to add a Typeform form to your website and that is using HTML or using JavaScript. This being said we will look at how you can track form interactions for both methods, so whatever you end choosing you can track your froms without problems. To kick things off, we will look at tracking form interactions with HTML embeds which is the default method of including Typeform forms on your website. To get started, after building your form and publishing it, click on the share button in the top menu and then select the Embed on a web page option in the left side panel. Then choose the format of the embed and click on Start Embedding. At this stage, a pop up window should appear with the header Embed your typeform with followed by: - Wordpress - Squarespace - Webflow - Shopify - General Embeds We are going to select General Embeds, but you can select whatever option you would like. This will not affect the upcoming steps. Here's what the embedding code will look like for a standard embed: ```html <button data-tf-popup="<form-id>" data-tf-opacity="100" data-tf-size="100" data-tf-iframe-props="title=Political Poll (copy)" data-tf-transitive-search-params data-tf-medium="snippet" style="all:unset;font-family:Helvetica,Arial,sans-serif;display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background-color:#0445AF;color:#fff;font-size:20px;border-radius:25px;padding:0 33px;font-weight:bold;height:50px;cursor:pointer;line-height:50px;text-align:center;margin:0;text-decoration:none;" > Try me! </button> <script src="//embed.typeform.com/next/embed.js"></script> ``` Firstly, if you are copy pasting this code snippet, please do not forget to replace the <form-id> with your actual form id otherwise the proceeding steps will not work. For the next step, we will be changing the code snippet by adding data-tf-on-submit="submit" to our button element. It should look like this: ```html <button data-tf-popup="<form-id>" data-tf-on-submit="submit" data-tf-opacity="100" data-tf-size="100" data-tf-iframe-props="title=Political Poll (copy)" data-tf-transitive-search-params data-tf-medium="snippet" style="all:unset;font-family:Helvetica,Arial,sans-serif;display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background-color:#0445AF;color:#fff;font-size:20px;border-radius:25px;padding:0 33px;font-weight:bold;height:50px;cursor:pointer;line-height:50px;text-align:center;margin:0;text-decoration:none;" > Try me! </button> ``` Once we have this in place, it is time to add the JS code snippet that will be executed once the form is successfully submitted. To log our event into the dataLayer, we will be using the onSubmit callback function. The onSubmit function fires when a user successfully submits the typeform by clicking the "Submit" button. Let's take a have a look at how to do this: ```html <script> // this function needs to be available on global scope (window) function submit(formId) { console.log(`Form ${formId} submitted`); } </script> ``` Let's put everything together: ```html <button data-tf-popup="<form-id>" data-tf-on-submit="submit" data-tf-opacity="100" data-tf-size="100" data-tf-iframe-props="title=Political Poll (copy)" data-tf-transitive-search-params data-tf-medium="snippet" style="all:unset;font-family:Helvetica,Arial,sans-serif;display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background-color:#0445AF;color:#fff;font-size:20px;border-radius:25px;padding:0 33px;font-weight:bold;height:50px;cursor:pointer;line-height:50px;text-align:center;margin:0;text-decoration:none;" > Try me! </button> <script src="//embed.typeform.com/next/embed.js"></script> <script> // this function needs to be available on global scope (window) function submit(formId) { console.log(`Form ${formId} submitted`); } </script> ``` Here's a breakdown of the different parts of the code: The button is created using HTML markup, and includes several data attributes that are used to configure the Typeform popup. These attributes include: - data-tf-popup: the ID of the Typeform form to display in the popup - data-tf-opacity: the opacity of the popup (in this case, set to 100%) - data-tf-size: the size of the popup (in this case, set to 100%) - data-tf-iframe-props: a list of properties to apply to the popup's iframe element - data-tf-medium: the medium used to embed the form (in this case, set to "snippet") - style: a list of CSS styles to apply to the buttonThe button also includes the text "Try me!". - The first script tag includes the Typeform embed.js script, which is required to create the Typeform popup. - The second script tag defines a JavaScript function named submit(), which logs a message to the console indicating that the specified Typeform form has been submitted. The function takes an object with a single property formId that specifies the ID of the submitted form. When a user clicks the button, the Typeform popup is displayed, allowing them to fill out and submit the specified form. When the user submits the form, the submit() function is called with the ID of the submitted form, and a message is logged to the console indicating that the form has been submitted. All right! Time to add Google Tag Manager into the mix. To send our form submit event to GTM, we will need to use the dataLayer. The dataLayer is a JavaScript object that is used to pass information from your website to your Tag Manager container. This information can be used by tags to fire events, send data to other services, or update the DOM. To send our event to Google Tag Manager, the following addition needs to be added to our code. ```html <button data-tf-popup="<form-id>" data-tf-on-submit="submit" data-tf-opacity="100" data-tf-size="100" data-tf-iframe-props="title=Political Poll (copy)" data-tf-transitive-search-params data-tf-medium="snippet" style="all:unset;font-family:Helvetica,Arial,sans-serif;display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background-color:#0445AF;color:#fff;font-size:20px;border-radius:25px;padding:0 33px;font-weight:bold;height:50px;cursor:pointer;line-height:50px;text-align:center;margin:0;text-decoration:none;" > Try me! </button> <script src="//embed.typeform.com/next/embed.js"></script> <script> window.dataLayer = window.dataLayer || []; // this function needs to be available on global scope (window) function submit({ formId }) { window.dataLayer.push({ event: "form_submit", form_id: `${formId}`, }); } </script> ``` You will notice two changes in the above code snippet: 1. The addition of window.dataLayer = window.dataLayer || []. This line intitializes Google Tag Manager's dataLayer. The line reads if the dataLayer exists then use it. Else, it will be defined as an empty array. 2. The addition of: ```javascript window.dataLayer.push({ event: "form_submit", form_id: `${formId}`, }); ``` This line logs the form submit to the dataLayer along with the id of the submitted form. This additional event parameter will help you identify which form has been submitted. Now that our form submit is logged into the dataLayer, we will need to configure a Google Analytics 4 event on Tag Manager to send this data to Google Analytics 4, but more on that later on. For now, let's continue our tracking. Since, our form submissions are being tracked, the next steps to have a proper funnely analysis is to track form initiations as well as questions changes. Using these events you can have precise measurements on your form interactions. Let's do jump in. To track these events, we are going to be adding two data attributes and defining two new JS functions. These functions will execute the code we need to log the events whenever the form initiates or a question changes. Let's have a look at our new code: ```html <button data-tf-popup="HRzdvnfw" data-tf-on-ready="ready" data-tf-on-question-changed="changed" data-tf-on-submit="submit" data-tf-opacity="100" data-tf-size="100" data-tf-iframe-props="title=Political Poll (copy)" data-tf-transitive-search-params data-tf-medium="snippet" style="all:unset;font-family:Helvetica,Arial,sans-serif;display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background-color:#0445AF;color:#fff;font-size:20px;border-radius:25px;padding:0 33px;font-weight:bold;height:50px;cursor:pointer;line-height:50px;text-align:center;margin:0;text-decoration:none;" > Try me! </button> <script src="//embed.typeform.com/next/embed.js"></script> <script> window.dataLayer = window.dataLayer || []; // this function needs to be available on global scope (window) function submit({ formId }) { window.dataLayer.push({ event: "form_submit", form_id: `${formId}`, }); } function ready({ formId }) { window.dataLayer.push({ event: "form_start", form_id: `${formId}`, }); } function changed({ formId, ref }) { window.dataLayer.push({ event: "question_changed", form_id: `${formId}`, questions_id: `${ref}`, }); } </script> ``` Let's start with the data attributes, shall we? We have added two new data attributes being data-tf-on-ready="ready" and data-tf-on-question-changed="changed". These attributes will basically execute the function ready when the form completely loads and the function changed whenever a question changes on the form, respectively. Let's breakdown the new JavaScript functions: ```js function ready({ formId }) { window.dataLayer.push({ event: "form_start", form_id: `${formId}`, }); } function changed({ formId, ref }) { window.dataLayer.push({ event: "question_changed", form_id: `${formId}`, questions_id: `${ref}`, }); } ``` The ready() function is called when the form is loaded. It pushes an event to the data layer with the event name form_start and the form ID. The changed() function is called when a question in the form is changed. It pushes an event to the data layer with the event name question_changed, the form ID, and the question ID. Now that we have the events we need logged into the dataLayer, it is time to configure our Google Analytics 4 event tag in Google Tag Manager. To do so, follow these steps: - **Step 1**: Go to your Google Tag Manager account - **Step 2**: Click the "Tags" tab. - **Step 3**: Click the "New" button. - **Step 4**: Select "Google Analytics: GA4 Event". - **Step 5**: In the "Configuration" section, enter your Google Analytics 4 tracking ID. - **Step 6**: In the Event Name field, use the {{Event}} variable - **Step 7**: In the Event Parameters, enter form_id in the first row with its value set to {{DLV - form_id}}. In the second row, enter question_id with its value set to {{DLV - question_id}}. We will define those values later. - **Step 8**: In the "Triggering" section, create a new trigger. Name the trigger whatever you please, we suggest something representative such as Typeform Form Interactions. For the tigger type, select Custom Event. In the Event name field, type form\_(start|submit)|question_changed. Make sure to check the Use regex matching option. - **Step 9**: Save your trigger and tag. - **Step 10**: It's time to create the dataLayer variables we used as values in our Event parameters. Head to the variables tab, and in the User-Defined Variables, click on Create. Choose Data Layer Variable as the varibale type and in the input field enter form_id. As for the variable name enter DLV - form_id. If you named the varibale differently in the Event Parameters, use the same name. Save the variable and repeat the same process to create the question_id variable. When you create this variable, use question_id as the value and DLV - question_id as the variable name. - **Step 11**: Click the "Save" button, preview and test your changes. ## Tracking Form Interactions for Vanilla JS Embeds Now that we have everything configured for HTML embedded elements, let's have a look at how we can track forms that have been embedded through JS. Embedding forms through JavaScript gives you more control over the forms and allows for the addition of custom behaviour. Let's look at how we can embed a form through JavaScript. ```html <button id="button">open form</button> <script src="//embed.typeform.com/next/embed.js"></script> <link rel="stylesheet" href="//embed.typeform.com/next/css/popup.css" /> <script> const { open, close, toggle, refresh } = window.tf.createPopup("<form-id>"); document.querySelector("#button").onclick = toggle; </script> ``` When we click the open form button, our form will load; pretty cool! As mentioned above, please do not forget to replace the <form-id> placeholder with your Typeform form's ID. Next up, we will add the tracking for the same events as the HTML embed forms. Let's have a look at how that looks like: ```html <script> "use strict"; window.dataLayer = window.dataLayer || []; const { open, close, toggle, refresh } = window.tf.createPopup("HRzdvnfw", { onReady: ({ formId }) => { console.log(`Form ${formId} is ready`); dataLayer.push({ event: "form_start", form_id: `${formId}`, }); }, onQuestionChanged: ({ formId, ref }) => { dataLayer.push({ event: "question_change", question: `${ref}`, }); }, onSubmit: ({ formId }) => { dataLayer.push({ event: "form_submit", form_id: `${formId}`, }); }, onEndingButtonClick: ({ formId }) => { dataLayer.push({ event: "form_close", form_id: `${formId}`, }); }, }); document.querySelector("#button").onclick = toggle; </script> ``` The script works as follows: 1. The script initializes the data layer. 2. The script creates a popup form. 3. The script adds an event listener to the "button" element. 4. When the "button" element is clicked, the script opens the popup form. 5. the popup form is opened, the script fires the "form_start" event. 6. When a question in the form is changed, the script fires the "question_change" event. 7. When the form is submitted, the script fires the "form_submit" event. 8. When the "Ending Button" is clicked, the script closes the popup form and fires the "form_close" event. As you can see, we added a new event for these embedded forms, but feel free to modify the provided code to track form interactions that are important to you. As for sending the events to Google Analytics 4 using Google Tag Manager, please follow the same steps highlighted above. That's folks! If you follow the steps highlighted above, you will be able to track the interactions on your Typeform forms using Google Tag Manager and Google Analytics.

Tracking E-commerce with Google Tag Manager

February 5, 2023

Five months separate us from the sunset of Universal Analytics. While many companies have started adopting Google Analytics 4, many are still using Universal Analytics especially E-commerce brands. While many E-commerce platforms have built Google Analytics 4 integrations, others still did not. By the way, if you are using Shopify, they will be launching their Google Analytics 4 integration in March. You can read more about that [here](https://help.shopify.com/en/manual/reports-and-analytics/google-analytics/google-analytics-4). But, if you do not want to wait for the integration to be released or you are working with other E-commerce platform, read along as we will be discussing how to track E-commerce events using Google Tag Manager. Before we move along, please check if your E-commerce platform supports Google Analytics 4. This should save you a lot of time. If there is no integration and you want to learn how to track user behaviour on your E-commerce store, let’s get started. ## E-commerce recommended events When it comes to online stores, Google Analytics 4 has a list of recommended events that every business should be implementing. The events also come with predefined parameters that help you better analyze your buyer’s journey. So, let’s discover the list of recommended events: <table> <thead> <tr> <th>Event</th> <th>Trigger when</th> </tr> </thead> <tbody> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_payment_info">add_payment_info</a></td> <td>a user submits their payment information</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_shipping_info">add_shipping_info</a></td> <td>a user submits their shipping information</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_to_cart">add_to_cart</a></td> <td>a user adds items to cart</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_to_wishlist">add_to_wishlist</a></td> <td>a user adds items to a wishlist</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#begin_checkout">begin_checkout</a></td> <td>a user begins checkout</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#generate_lead">generate_lead</a></td> <td>a user submits a form or a request for information</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#purchase">purchase</a></td> <td>a user completes a purchase</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#refund">refund</a></td> <td>a user receives a refund</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#remove_from_cart">remove_from_cart</a></td> <td>a user removes items from a cart</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#select_item">select_item</a></td> <td>a user selects an item from a list</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_cart">view_cart</a></td> <td>a user views their cart</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_item">view_item</a></td> <td>a user views an item</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_item_list">view_item_list</a></td> <td>a user sees a list of items/offerings</td> </tr> </tbody> </table> The following events will give you full visibility over your user buying journey and help you make informed decisions about optimizing your marketing campaigns as well as funnels. In Google Analytics 4, E-commerce related events come with two types of parameters. The first type is the event parameters and the second type is items parameters. Event parameters add context about the E-commerce event. Item parameters help send information about the items that are involved in said action. For instance, let’s look at the add_to_cart event in order to understand the difference between event parameters and item parameters. > 💡 All the discussed events in this article can be generated using this tool. ## The difference between event parameters and item parameters As indicated in the above table, the add_to_cart event is to be triggered when the users adds items to the cart. Let’s look at the taxonomy of the event: ```js dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. dataLayer.push({ event: "add_to_cart", ecommerce: { currency: "USD", value: 7.77, items: [ { item_id: "SKU_12345", item_name: "Stan and Friends Tee", affiliation: "Google Merchandise Store", coupon: "SUMMER_FUN", discount: 2.22, index: 0, item_brand: "Google", item_category: "Apparel", item_category2: "Adult", item_category3: "Shirts", item_category4: "Crew", item_category5: "Short sleeve", item_list_id: "related_products", item_list_name: "Related Products", item_variant: "green", location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo", price: 9.99, quantity: 1, }, ], }, }); ``` In this example, our event parameters are currency, value, and items . As mentioned above, this data gives more context about the event itself. The currency gives us more information about the currency the user is currently using on the store. The value parameter gives information about the monetary value of the event; for example 30. With the information at hand and using the event’s data as reference, we can read a user added 7.77 US dollars worth of our products to the cart. While this is helpful, it does not give a full picture. This is where events parameters come into play. Using this additional data, we can know which product(s) was added to the cart, the quantity added, and other relevant information about the product like the variant. You might ask how can this help, so let’s answer this question with an example as it will highlight how powerful having the right item parameters can be. Let’s imagine that you are an analyst at retail company and the new creative team just launched 5 new designs across 3 categories of clothing. You are tasked to deliver a report on the most visited, add to cart and purchased variants per clothing category. Using only event parameters answering this question will be very difficult. But, let’s take a look at the items array: ```json { "items": [ { "item_id": "SKU_12345", "item_name": "Stan and Friends Tee", "affiliation": "Google Merchandise Store", "coupon": "SUMMER_FUN", "discount": 2.22, "index": 0, "item_brand": "Google", "item_category": "Apparel", "item_category2": "Adult", "item_category3": "Shirts", "item_category4": "Crew", "item_category5": "Short sleeve", "item_list_id": "related_products", "item_list_name": "Related Products", "item_variant": "green", "location_id": "ChIJIQBpAG2ahYAR_6128GcTUEo", "price": 9.99, "quantity": 1 } ] } ``` The items array is made of objects. In our example, the items array is only made of 1 object. Going over the item parameters, let’s take a look at the item_variant as well as item_category2. Using these 2 parameters, we can build a chart that helps us answer the question above. Back to our regular programming. With the event parameters, all we could read is a user added 7.77 US dollars worth of our products to the cart. Now, we can read way more; way way more. Using the two parameters we chose earlier (item_category and item_category5, we can read a user added a short sleeve worth 7.77 US dollars from our apparel products to their cart. Now things are much clearer. We can read into the data better if we replace the item_category with item_category2 and we add the discount parameter. Using this data, we can read a user added an adult short sleeve worth 9.99 US dollars to their cart and applied a 2.22 discount. This sentence clearly describes the user behaviour. Hopefully, the example above highlighted the difference between event parameters and item parameter and the power of combining this contextual data to draw clear pictures of user behaviour. ## Tracking non checkout events To make things easier for everyone, E-commerce events are going to be categorized into non checkout events and checkout events. If you are interesting in tracking the checkout behaviour including refunds, please skip to that section. ### add_to_cart event This is one of the most commonly tracked events for online store. So, let’s learn how to track it. The most common way to add a product to the cart is to use the add to cart button present on every product detail page. So, the best way to track this would be add use the addEventListener method. In short, we want that every time a user tracks the add to cart button, we trigger the add_to_cart event. For demonstration purposes, we will use a simple product detail page. If your implementation is much more complex, the implementation is the same. You will need to adjust the code a little bit but the concept is the same. Let’s dig in. The first step is to make sure that we are able to properly identify the Add To Cart button. Without this button, our efforts will be in vain. The next step is to make sure we have accessible elements on the page that clearly indicated: - quantity - product’s name - any other metadata to be sent with the add to cart event Now that we know that we can access the data, let’s understand the procedure of the implementation: - Once the user clicks on the Add To Cart button, we will fire an add_to_cart event using the addEventListener method - The function to be executed will log the add_to_cart event into the dataLayer for later use - The function will make sure the dataLayer is declared and if not declare it - It will clear the previous ecommerce object - It will log the data we need into the dataLayer Let’s have a look at the code that will do what we just described: ```js // Sending an add to cart event every time the user click on the Add to Cart button const addToCartButton = document.querySelector('a.round-black-btn[href="#"]'); const regexNum = "[+-]?([0-9]*[.|,])?[0-9]+"; //Regular Expression to extract numbers from a string const getProductPrice = () => { let stringProductPrice = document.querySelector(".product-price").textContent; // Getting the price as a string let numbersOnlyStringProductPrice = stringProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let productPrice = Number(numbersOnlyStringProductPrice); // Changing the price's data type into a number return productPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called }; const getCartValue = () => { let quantityAdded = document.querySelector('input[name="quantity"]').value; let productUnitPrice = getProductPrice(); let cartValue = productUnitPrice * quantityAdded; return cartValue; // The return statement ensures that cartValue is the output of the getCartValue every time the function is called }; // Defining the add to cart function const addToCart = () => { console.log("clicked"); window.dataLayer = window.dataLayer || []; // Declaring the dataLayer object if not defined dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. window.dataLayer.push({ event: "add_to_cart", // Event name. Do not change unless necessary ecommerce: { currency: "USD", value: getCartValue(), // Using the function getCartValue to calculate the cart's total value items: [ { item_name: document.querySelector("div.product-name").textContent, price: getProductPrice(), // Using the getProductPrice function to fetch the product's unitary price quantity: document.querySelector('input[name="quantity"]').value, }, ], }, }); }; addToCartButton.addEventListener("click", addToCart); // Every time the addToCartButton is clicked, the addToCart function will be called. ``` This code block is a JavaScript code that sends an 'add to cart' event every time the user clicks on the 'Add to Cart' button. The code uses the querySelector method to select the 'Add to Cart' button and assign it to the addToCartButton constant. The getProductPrice function uses a regular expression (regexNum) to extract numbers from a string representation of the product price and converts it to a number. The getCartValue function calculates the total value of the items in the cart by multiplying the product unit price with the quantity added. The addToCart function declares and initializes the dataLayer object, which is used to store data that is sent to Google Tag Manager. The function pushes an 'add to cart' event with details such as the currency, value, and items in the cart to the dataLayer object. Finally, the code adds an event listener to the addToCartButton, which calls the addToCart function every time the button is clicked. Now that we are logging our add_to_cart event to the dataLayer, let’s take a minute to discuss the items array. This is an important part of E-commerce tracking, so it is important to make sure that it is well understood. The items array is required to for every E-commerce event. Please not that it does not mean that it needs to be populated. If the items array is null, the event is still valid. The items array is composed of one or more object with each object describing a specific product. Google’s documentation specifies which keys can be passed within the object. Here’s the exhaustive list: | Name | Type | Required | Example value | Description | | ---------------- | ------ | -------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `item_id` | string | Yes\* | SKU_12345 | The ID of the item. \*One of `item_id` or `item_name` is required. | | `item_name` | string | Yes\* | Stan and Friends Tee | The name of the item. \*One of `item_id` or `item_name` is required. | | `affiliation` | string | No | Google Store | A product affiliation to designate a supplying company or brick and mortar store location. Note: `affiliation` is only available at the item-scope. | | `coupon` | string | No | SUMMER_FUN | The coupon name/code associated with the item. Event-level and item-level `coupon` parameters are independent. | | `discount` | number | No | 2.22 | The monetary discount value associated with the item. | | `index` | number | No | 5 | The index/position of the item in a list. | | `item_brand` | string | No | Google | The brand of the item. | | `item_category` | string | No | Apparel | The category of the item. If used as part of a category hierarchy or taxonomy then this will be the first category. | | `item_category2` | string | No | Adult | The second category hierarchy or additional taxonomy for the item. | | `item_category3` | string | No | Shirts | The third category hierarchy or additional taxonomy for the item. | | `item_category4` | string | No | Crew | The fourth category hierarchy or additional taxonomy for the item. | | `item_category5` | string | No | Short sleeve | The fifth category hierarchy or additional taxonomy for the item. | | `item_list_id` | string | No | related_products | The ID of the list in which the item was presented to the user. If set, event-level `item_list_id` is ignored. If not set, event-level `item_list_id` is used, if present. | | `item_list_name` | string | No | Related products | The name of the list in which the item was presented to the user. If set, event-level `item_list_name` is ignored. If not set, event-level `item_list_name` is used, if present. | | `item_variant` | string | No | green | The item variant or unique code or description for additional item details/options. | | `location_id` | string | No | ChIJIQBpAG2ahYAR_6128GcTUEo (the Google Place ID for San Francisco) | The physical location associated with the item (e.g. the physical store location). It's recommended to use the [Google Place ID](https://developers.google.com/maps/documentation/places/web-service/place-id) that corresponds to the associated item. A custom location ID can also be used. Note: `location id` is only available at the item-scope. | | `price` | number | No | 9.99 | The monetary price of the item, in units of the specified currency parameter. | | `quantity` | number | No | 1 | Item quantity. If not set, `quantity` is set to 1. | These item parameters are the same for all E-commerce events. Just make sure your events have the same parameters to create continuity for your tracking. ### add_to_wishlist Since we have our add_to_cart event properly implemented, we can use the same logic to track the rest of the events. Let’s have a look at the add_to_wishlist event. This event should be triggered whenever a user adds a product to their wish list. That being said, here are some things to consider working with this event: - The parameter will not be used with this event since we adding a singular product to the wish list - The value of the event is equal to the unitary value of the product These points should make this a event a bit easier to work with compared to the add_to_cart event. Let’s have a look at the code block we will be using to track this event: ```js // Sending an add to wish list event every time the user click on the Add to Wish List button const addToWishListButton = document.querySelector( 'a.round-black-btn.wishlist[href="#"]', ); // Defining the add to cart function const addToWishList = () => { console.log("clicked"); window.dataLayer = window.dataLayer || []; // Declaring the dataLayer object if not defined dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. window.dataLayer.push({ event: "add_to_wishlist", // Event name. Do not change unless necessary ecommerce: { currency: "USD", value: getCartValue(), // Using the function getCartValue to calculate the cart's total value items: [ { item_name: document.querySelector("div.product-name").textContent, price: getProductPrice(), // Using the getProductPrice function to fetch the product's unitary price }, ], }, }); }; addToWishListButton.addEventListener("click", addToWishList); // Every time the addToWishList is clicked, the addToWishList function will be called. ``` This code is used to track an "add_to_wishlist" event in Google Tag Manager whenever the user clicks on the "Add to Wish List" button. The "Add to Wish List" button is selected using the query selector and is assigned to the "addToWishListButton" constant. The "addToWishList" function is defined to push the event to the dataLayer. If the dataLayer object does not exist, it is declared and the previous ecommerce object is cleared. The function then pushes the "add_to_wishlist" event with the event details to the dataLayer. The "addToWishListButton" is then assigned an event listener to listen for clicks. When the button is clicked, the "addToWishList" function is called, which pushes the "add_to_wishlist" event to the dataLayer. The function also uses the "getProductPrice" and "getCartValue" functions to fetch the product's unitary price and calculate the cart's total value, respectively. And that’s that! You have successfully tracked two E-commerce events. Let’s keep it going and look at the generate_lead event. ### generate_lead This event, while not an E-commerce event, is on the recommended events list. You can use this event to understand how your different newsletters are growing. Essentially, every time a user signs up to a newsletter, we will generate this event and add the newsletter’s title in order for us to be more informed about which newsletters are driving the most leads. Since we are going to be working with form submissions, we will be listening for the submission of the form and we will be sending the event accordingly. Please not that we are not going to do any client-side validation for the form. If you want to read more about said topic, follow this [link](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation). Let’s have a look at the code we will be using to track the generate_lead event. Here’s the code section for our form. ```html <!-- This is our form --> <!-- For the sake of this exercise, we will consider that our newsleter's name is equivalent to the form's id --> <div class="newsletter_signup"> <form action="#" id="product_releases"> <label for="user_email" >Subscribe to our newsletter to stay up to date with out drops!</label > <input type="email" name="email" id="user_email" placeholder="Enter your email. We will never share it!" required /> <input type="submit" value="Subscribe" id="subscribe_button" /> </form> </div> ``` Let’s have a look at the JavaScript now: ```js // Identifying the newsletter form const newsletter_form = document.querySelector("form#product_releases"); const newsletter_name = newsletter_form.getAttribute("id"); // Sending the generate_lead event every time the newsletter form is submitted const generateLead = (e) => { e.preventDefault(); // This is to prevent the default behaviour of the submit event window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "generate_lead", newsletter_name: newsletter_name, }); }; newsletter_form.addEventListener("submit", generateLead); ``` This code block is for sending a "generate_lead" event every time the newsletter form is submitted. The first step is identifying the newsletter form using the document.querySelector method and storing it in the newsletter_form variable. Then, the id of the form is extracted using the .getAttribute method and stored in the newsletter_name variable. The next step is defining the generateLead function which will be triggered every time the form is submitted. The function uses the preventDefault method to prevent the default behaviour of the submit event. It also initializes the dataLayer object and pushes the "generate_lead" event along with the newsletter_name. Finally, the addEventListener method is used to listen for the submit event on the newsletter_form and call the generateLead function every time it occurs. That’s a wrap for this event. Next up on the list, it’s the select_item event. ### select_item As defined by the list above, this event should fire whenever a use selects an item from a list. To put it in other terms, whenever a user clicks on an item to view from a given list, the event needs to be triggered and the appropriate data needs to be sent with it. Let’s dig right in! Let’s imagine that our recommended products list looks something like this: ```html <div class="card-group"> <a class="card" href="#"> <div class="card-body"> <h5 class="card-title">Related Product 1</h5> <p class="cart-text text-muted product-price">$20.00</p> <p class="card-text"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vitae vestibulum arcu. Ut lobortis non augue quis scelerisque. Nulla vitae diam tortor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </a> <a class="card" href="#"> <div class="card-body"> <h5 class="card-title">Related Product 2</h5> <p class="cart-text text-muted product-price">$20.00</p> <p class="card-text"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vitae vestibulum arcu. Ut lobortis non augue quis scelerisque. Nulla vitae diam tortor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </a> <a class="card" href="#"> <div class="card-body"> <h5 class="card-title">Related Product 3</h5> <p class="cart-text text-muted product-price">$20.00</p> <p class="card-text"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vitae vestibulum arcu. Ut lobortis non augue quis scelerisque. Nulla vitae diam tortor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </a> </div> ``` This list shows 3 product that are related to the current product. So, the goal is to fire the select_item event whenever the user clicks on the product card. Since, this the code we have is just for demonstration, clicking the card will not open a new page but the event should fire the same way. > 💡 Please note that the event should fire whenever a user clicks on a related product card regardless of the card itself. Also, the parameters sent with the event should always be the same. Let’s take a look at the JavaScript that should allow us to this: ```js // Getting the list of related products const relatedProducts = document.querySelectorAll('a[href=""].card'); // itemArray let itemArray = []; // We will use this array to sedn the data into the items array inside the dataLayer event // selectItem function const selectItem = (e) => { itemArray.push({ item_name: e.target.closest('a.card').querySelector('h5.card-title').textContent, price: function() { let stringRelatedProductPrice = document.querySelector('.product-price').textContent; // Getting the price as a string let numbersOnlyStringRelatedProductPrice = stringRelatedProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let RelatedproductPrice = Number(numbersOnlyStringRelatedProductPrice); // Changing the price's data type into a number return RelatedproductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called }() }); // Defining the dataLayer window.dataLayer = window.dataLayer || []; window.dataLayer.push({ecommerce: null}); // Clear the previous ecommerce object. window.dataLayer.push({ 'event': 'select_item', 'ecommerce': { item_list_id: "related_products", item_list_name: "Related products", 'items': itemArray } }); }); } relatedProducts.forEach((relatedProduct) => { relatedProduct.addEventListener('click', selectItem); }); ``` This code block is tracking the clicks on related products on a webpage. It starts by getting the list of related products by selecting all elements with class "card" and a href attribute equal to an empty string. This information is stored in the relatedProducts variable. The code then creates an empty array itemArray which will be used to store the data of each related product that is clicked. A selectItem function is defined which takes in the event object and pushes the name and price of the related product that was clicked into the itemArray. This function is then called every time a related product is clicked by adding an event listener to each related product element. When the selectItem function is called, it first pushes the name and price of the related product that was clicked into the itemArray. It then defines the dataLayer object and pushes the select_item event and the related product data into it. The end result of this code is that every time a user clicks on a related product, the select_item event will be triggered and the data of the related product will be sent to the dataLayer. That’s that for this event. Let’s look into the next one which is view_cart. ### view_cart This is one of the new E-commerce events introduced in Google Analytics 4. This event will help you to know how many customers are actually visiting the cart. The event can be used to gauge the propensity to purchase. Most of E-commerce websites have side carts that open when the user wants to look at what they have in the cart. Maybe users want to know the value of the cart or to add/remove some items. Either way, we are going to use this event to make sure that whenever the cart shows, we are able to track it. Another scenario we will look at is when users are taken to a cart page (/cart). Let’s dig into the first scenario where users will interact with a side cart. Before we dive into the code block and its different section, note that we are using Bootstrap 5 to build the “side cart”. In this particular case, we will be using the Offcanvas component. This important for you to know since we will be using the JavaScript events of this component to trigger the view_cart event. You can read more the component and its event [here](https://getbootstrap.com/docs/5.0/components/offcanvas/#events). Let’s dig into the code. ```js let sideCartProductsArray = []; let sideCartValue = 0; const viewCart = () => { let sideCartProducts = document.querySelectorAll( "div.side-cart li.list-group-item", ); if (sideCartProducts) { sideCartProducts.forEach((sideCartProduct) => { sideCartProductsArray.push({ item_name: sideCartProduct.querySelector(".product-title").textContent, price: (function () { let sideCartStringProductPrice = sideCartProduct.querySelector(".product-price").textContent; let sideCartNumbersOnlyStringProductPrice = sideCartStringProductPrice.match(regexNum)[0]; let sideCartProductPrice = Number( sideCartNumbersOnlyStringProductPrice, ); return sideCartProductPrice; })(), }); }); for ( let sideCartProductValue = 0; sideCartProductValue < sideCartProductsArray.length; sideCartProductValue++ ) { sideCartValue += sideCartProductsArray[sideCartProductValue].price; } window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "view_cart", ecommerce: { currency: "USD", value: sideCartValue, items: sideCartProductsArray, }, }); } }; // Getting the side cart and firing the event whenever the side cart is shown let sideCart = document.getElementById("offcanvasExample"); sideCart.addEventListener("shown.bs.offcanvas", () => { console.log("Cart viewed"); viewCart(); }); ``` The code block starts by declaring two variables: sideCartProductsArray and sideCartValue. sideCartProductsArray is an empty array that will store information about the products in the side cart, while sideCartValue is a variable that will store the total value of the products in the side cart. Next, the function viewCart is declared. This function is called every time the user views the side cart. The function starts by using the querySelectorAll method to get all the products in the side cart (which are stored in li elements with the class list-group-item inside a div with class side-cart). If there are products in the side cart, the code block loops through each product and pushes the product's information (name and price) into the sideCartProductsArray. The price of each product is obtained by using a regular expression (regex) to extract the numbers from the product's price string and converting it to a number using the Number method. The total value of the products in the side cart is then calculated by looping through the sideCartProductsArray and adding the price of each product. Finally, the dataLayer is declared and the view_cart event is pushed into the dataLayer. The event contains information about the currency (USD) and the value of the side cart, as well as the products in the side cart. The code block ends by using the getElementById method to get the side cart and adding an event listener to the side cart. The event listener is for the shown.bs.offcanvas event and it is triggered every time the side cart is shown. When this event is triggered, the viewCart function is called and the "view_cart" event is sent to Google Analytics. That’s it for the first scenario where a side cart is shown to the user. This way of tracking is more complicated then on a page view. If the you want to fire the same event using the /cart page load as the trigger, you can do so by modifying the trigger from the shown.bs.offcanvas to load. This is admitting the the same CSS selectors are being used on the /cart page. If not then simply modify the CSS selectors to make sure you are getting what you need. ### view_item If you are still having trouble working with triggering an event on page load, you are in luck! The view_item event, as described above, is an event that will fire the whenever a user views an item. To make things clearer, we want to fire this event whenever a user visit a product details page. With this in mind, we should be able to track this event with relative ease. Here’s what the code will look like: ```js // Defining the add to cart function const viewItem = () => { window.dataLayer = window.dataLayer || []; // Declaring the dataLayer object if not defined dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. window.dataLayer.push({ event: "view_item", ecommerce: { currency: "USD", value: getProductPrice(), items: [ { item_name: document.querySelector("div.product-name").textContent, price: getProductPrice(), }, ], }, }); }; window.addEventListener("load", viewItem); ``` This code block defines a function named viewItem. This function pushes data to the Google Tag Manager's dataLayer. The data includes an event name, which is "view_item", as well as an ecommerce object that includes information such as the currency used (USD), the item's value (obtained from a function named getProductPrice), and information about the item itself (obtained from the DOM, such as the item's name and its price). The function is triggered by a load event, so it will fire whenever the page finishes loading. ### view_item_list This is one of the trickiest events to work with. First, based on the definition, we can fire the event when the user is on the collections page or when they have viewed a recommended list for related products or for the recently viewed products. For this to work, we will be using the IntersectionObserver API. If you are not familiar it, have a [read](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). The goal of this procedure is to fire the view_item_list whenever the recommended products list enters into view. Let’s have a look at the code. ```js // creating the observer for the recommended products list let options = { threshold: 1.0, }; // creating observedProductsArray. This will be the value of the items array in the dataLayer let observedProductsArray = []; const hasBeenSeen = (entries) => { if (entries) { // Firing this event only when we are sure that the intersection happened let observedProductsList = document.querySelectorAll( "div.recommended-products a.card", ); // List of observed products observedProductsList.forEach((observedProduct) => { observedProductsArray.push({ item_name: observedProduct.querySelector("h5").textContent, price: (function () { let ObservedStringProductPrice = observedProduct.querySelector(".product-price").textContent; let numbersOnlyStringObservedProductPrice = ObservedStringProductPrice.match(regexNum)[0]; let observedProductPrice = Number( numbersOnlyStringObservedProductPrice, ); return observedProductPrice; })(), }); }); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "view_item_list", ecommerce: { item_list_id: entries[0].target.getAttribute("data-list-id"), // entries is an object item_list_name: entries[0].target.getAttribute("data-list-name"), items: observedProductsArray, }, }); } observer.unobserve( document.querySelector("div.recommended-products div.card-group"), ); // Unobserving the element so the callback function will only get called once }; let observer = new IntersectionObserver(hasBeenSeen, options); observer.observe( document.querySelector("div.recommended-products div.card-group"), ); ``` First, we create a constant variable called "options" that is an object with a single property, "threshold". This threshold property is set to 1.0, which means that the event in question will only fire when the entire recommended products section is fully visible on the user's screen. Next, we create an empty array called "observedProductsArray". This array will be used to store information about the products in the recommended products section so that we can send this data to Google Tag Manager . We then define a function called "hasBeenSeen" which will be used as the callback function for the Intersection Observer. This function takes in a single argument, "entries". Entries is an object that contains information about the intersection of the observer and the target element (in this case, the "div.recommended-products div.card-group" element). If there is an intersection, we first retrieve all the products in the recommended products section using the DOM querySelectorAll method. We then loop through each product using a forEach loop and push each product's information (the product name and price) into the "observedProductsArray". Finally, we push the event "view_item_list" and the ecommerce information (which includes the item list ID, item list name, and the items in the list which is stored in the "observedProductsArray") to the dataLayer. After the event has been fired and the data has been logged into the dataLayer, we use the "unobserve" method of the observer to stop observing the target element. This is done to prevent the callback function from being fired multiple times. To set up the observer, we create an instance of the Intersection Observer and pass in two arguments: the callback function (in this case, the "hasBeenSeen" function) and the options object that we created earlier. Finally, we use the "observe" method of the observer to start observing the target element. Hopefully this explanation, however brief, helps you understand the code better. Now, let’s talk about the E-commerce pages with multiple lists. If you have more than one product list, then it is recommended to add multiple observer to the right lists to avoid complicated the tracking code. If you are comfortable working with loops that you can create a loop to iterate over all lists that you want to target and use the loop to create the Intersection Observers and track the lists whenever they are 100% in the users’ view port. As mentioned above, it is up to you whether you want to fire the view_item_list based on users visiting the collections page or not. Whatever you choose as suitable trigger for this event, make sure that all the item properties to be sent are easily accessible. While it is important to track events to have better visibility over user behaviour, remember that non-optimized tracking code blocks can cause performance issues or even may interfere with the way your web pages work. ## Tracking checkout events Now that everything is set prior to checkout, the next step is to focus on the checkout events which are begin_checkout, add_payment_info, add_shipping_info, and purchase. The first event we will start with is begin_checkout. ### begin_checkout This event is very straightforward. The goal is to send an event to Google Analytics 4 every time a user begins a checkout. If you are wandering if the event is going to fire for every occurrence, then the answer is yes. The reason for that is that it reflects the actual behaviour of your users. Let’s dig in. In our example, the checkout button is shown to the user once the user views their cart. Now, the goal is to fire the begin_checkout event once the user clicks on the checkout button and we pass the cart data to the event. Let’s see what that code looks like: ```js let checkoutProductsArray = []; let checkoutProductsValue = 0; const beginCheckout = () => { let checkoutProducts = document.querySelectorAll( "div.side-cart li.list-group-item", ); if (checkoutProducts) { checkoutProducts.forEach((checkoutProduct) => { checkoutProductsArray.push({ item_name: checkoutProduct.querySelector(".product-title").textContent, price: (function () { let checkoutStringProductPrice = checkoutProduct.querySelector(".product-price").textContent; let checkoutNumbersOnlyStringProductPrice = checkoutStringProductPrice.match(regexNum)[0]; let checkoutProductPrice = Number( checkoutNumbersOnlyStringProductPrice, ); return checkoutProductPrice; })(), }); }); for ( let beginCheckoutProductValue = 0; beginCheckoutProductValue < checkoutProducts.length; beginCheckoutProductValue++ ) { checkoutProductsValue += checkoutProductsArray[beginCheckoutProductValue].price; } window.dataLayer = window.dataLayer || []; window.dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. window.dataLayer.push({ event: "begin_checkout", ecommerce: { currency: "USD", value: checkoutProductsValue, items: checkoutProductsArray, }, }); } }; ``` Here's what the code does step by step: 1. It initializes two variables checkoutProductsArray and checkoutProductsValue as an empty array and zero, respectively. 2. It declares the beginCheckout function that will track the checkout events. 3. It selects all the li elements with a class of list-group-item inside a div element with a class of side-cart. These elements represent the products in the shopping cart. 4. If the checkoutProducts exist, the code will loop through each product and add the product information to the checkoutProductsArray as an object. The object has two properties: item_name and price. The item_name is extracted from the product's title (.product-title) and price is calculated using a function. The function first extracts the string value of the price (.product-price), and using a regular expression, it only keeps the numbers and converts it to a number. 5. The checkoutProductsValue is then calculated by adding up the prices of each product in the checkoutProductsArray. 6. The code initializes the dataLayer array and clears the previous ecommerce object (if any) by pushing { ecommerce: null } into the dataLayer. 7. The code then pushes an object into the dataLayer with the following properties: - event: "begin_checkout" - ecommerce: an object with the following properties - currency: "USD" - value: checkoutProductsValue - items: checkoutProductsArray This code is used to track the checkout process events in Google Tag Manager. ### add_payment_information & add_shipping_info These steps, if tracked, will help you establish a checkout funnel that you can use in order to gain insights in your checkout abandonment and build cart-recovery programs that will help you increase your revenue. However, to track these events outside of platform that offer direct integration with Google Analytics 4 or Shopify Plus, it is recommended to work with a developer to get it done. The reason is simple. These events need to be triggered once a user successfully ads their payment information and successfully ads their shipping information. In order to properly trigger these events, you will probably be using API response codes that indicate that payment information is valid or that the submitted address exists. That being said, it’s better to work with a developer to get these events tracked right. Here are some resources about the events: - [add_shipping_info](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_shipping_info) - [add_payment_info](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_payment_info) ### purchase This is perhaps one of the most important E-commerce events you would want to track. That being said, it is also better to work with developers to track this event if you are not using an an E-commerce that is integrated with Google Analytics 4 or something like Shopify where we can insert tracking codes easily. Here’s the documentation for the [purchase](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#purchase) event. There are somethings you would want to consider before moving to the implementation: - You want the event to fire after you have received an API response that the payment has been successfully processed and not when the purchase button is clicked - If you firing the event based on a thank you page, please make sure to the fire the event on first access and not on every page load - Make sure that the items involved in the transaction are available to be used wherever you are trying to implement the purchase event ## Using Google Tag Manager with the dataLayer to send events to Google Analytics 4 So far, our tracking is logging our E-commerce events data into the dataLayer but this is not enough to send the data to Google Analytics 4. All we did so far is step one of a two-step process. The next step is more straightforward thanks to the magic of Google Tag Manager containers. To expedite the process, we are going to rely on already-built E-commerce container. So, let’s get started! First, head to this [page](https://www.datakyu.co/resources/google-tag-manager-container-tempaltes.html) and download the E-commerce container template. The second step is to head to your Google Tag Manager container and import the container you just downloaded. Make sure that you merge the container and not override any settings you have. Before merging, make sure to inspect your current configuration and the new tags, triggers and variables to ensure nothing is causing any conflicts; only then do you proceed with the merge. Once the container has been properly installed, you will notice some new tags: - login - search - select_content - share - sign_up And the last new tag is All E-commerce Events. By way, if you do not feel comfortable keeping the new tags, you can always delete them. They are not required to have a functional E-commerce tracking. Let’s dig into the E-commerce tag. First and foremost, make sure to change the Measurement ID for the Google Analytics 4 Configuration Tag. As you can see the tag is configured to send an event which is equivalent to the current dataLayer event. We are able to do this using the Event built-in Event variable. You can also see that the tag is setting the user_id. You can remove this if you removed the user events. In fact, it is recommended that you remove this if you store’s Privacy Policy is not in top shape. The last thing about this tag is that it is using an additional setting Send Ecommerce data and that the data source is set to the Data Layer. This will ensure that whenever we are logging data into that dataLayer, the tag will use that data to populate the E-commerce data and then send along with the event. Once all the changes have been implemented, the next step is to test the implementation and see if everything is working as expected. If everything looks good, then publish your container and you are off to the races.

Tracking E-commerce with gtag.js

February 6, 2023

In our last issue, we discussed how to use Google Tag Manager to track E-commerce events and send them to Google Analytics 4. While many brands prefer to go this route for their tracking, there another route to take. We are, of course, talking about gtag.js. If you are not familiar with gtag.js, it is a JavaScript library that allows websites to send data to Google Analytics and track user behavior on a website. It provides a unified way of tracking various events such as pageviews, clicks, conversions, and more. gtag.js is the latest version of Google Analytics tracking code and is designed to support multiple Google measurement tools, including Google Analytics, Google Tag Manager, and Google AdWords. During this "tutorial", we are going to use this library to track the E-commerce events we want. Please make sure to review your code with a developer before launching the tracking. Unlike tracking with the dataLayer, gtag.js will send the call directly to Google Analytics 4, and you will see the events in your property. One way to avoid this is to have a property for testing or to use the filter in Google Analytics 4 to exclude developer traffic. We recommend creating a separate property for testing and the switching the measurement ID when you are ready to push your code to production. Before we move along, please check if your E-commerce platform supports Google Analytics 4. This should save you a lot of time. If there is no integration and you want to learn how to track user behaviour on your E-commerce store, let’s get started. ## E-commerce recommended events When it comes to online stores, Google Analytics 4 has a list of recommended events that every business should be implementing. The events also come with predefined parameters that help you better analyze your buyer’s journey. So, let’s discover the list of recommended events: <table> <thead> <tr> <th>Event</th> <th>Trigger when</th> </tr> </thead> <tbody> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_payment_info">add_payment_info</a></td> <td>a user submits their payment information</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_shipping_info">add_shipping_info</a></td> <td>a user submits their shipping information</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_to_cart">add_to_cart</a></td> <td>a user adds items to cart</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#add_to_wishlist">add_to_wishlist</a></td> <td>a user adds items to a wishlist</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#begin_checkout">begin_checkout</a></td> <td>a user begins checkout</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#generate_lead">generate_lead</a></td> <td>a user submits a form or a request for information</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#purchase">purchase</a></td> <td>a user completes a purchase</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#refund">refund</a></td> <td>a user receives a refund</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#remove_from_cart">remove_from_cart</a></td> <td>a user removes items from a cart</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#select_item">select_item</a></td> <td>a user selects an item from a list</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_cart">view_cart</a></td> <td>a user views their cart</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_item">view_item</a></td> <td>a user views an item</td> </tr> <tr> <td><a href="https://developers.google.com/analytics/devguides/collection/ga4/reference/events#view_item_list">view_item_list</a></td> <td>a user sees a list of items/offerings</td> </tr> </tbody> </table> The following events will give you full visibility over your user buying journey and help you make informed decisions about optimizing your marketing campaigns as well as funnels. In Google Analytics 4, E-commerce related events come with two types of parameters. The first type is the event parameters and the second type is items parameters. Event parameters add context about the E-commerce event. Item parameters help send information about the items that are involved in said action. For instance, let’s look at the add_to_cart event in order to understand the difference between event parameters and item parameters. > All the discussed events in this article can be generated using this tool. ## The difference between event parameters and item parameters As indicated in the above table, the add_to_cart event is to be triggered when the users adds items to the cart. Let’s look at the taxonomy of the event: ```js gtag("event", "add_payment_info", { currency: "USD", value: 22.3, items: [ { item_id: "SKU_12345", item_name: "Stan and Friends Tee", affiliation: "Google Merchandise Store", coupon: "SUMMER_FUN", currency: "USD", discount: 2.22, index: 0, item_brand: "Google", item_category: "Apparel", item_category2: "Adult", item_category3: "Shirts", item_category4: "Crew", item_category5: "Short sleeve", item_list_id: "related_products", item_list_name: "Related Products", item_variant: "green", price: 9.99, quantity: 1, }, ], }); ``` In this example, our event parameters are currency, value, and items . As mentioned above, this data gives more context about the event itself. The currency gives us more information about the currency the user is currently using on the store. The value parameter gives information about the monetary value of the event; for example 30. With the information at hand and using the event’s data as reference, we can read a user added 7.77 US dollars worth of our products to the cart. While this is helpful, it does not give a full picture. This is where events parameters come into play. Using this additional data, we can know which product(s) was added to the cart, the quantity added, and other relevant information about the product like the variant. You might ask how can this help, so let’s answer this question with an example as it will highlight how powerful having the right item parameters can be. Let’s imagine that you are an analyst at retail company and the new creative team just launched 5 new designs across 3 categories of clothing. You are tasked to deliver a report on the most visited, add to cart and purchased variants per clothing category. Using only event parameters answering this question will be very difficult. But, let’s take a look at the items array: ```js items: [ { item_id: "SKU_12345", item_name: "Stan and Friends Tee", affiliation: "Google Merchandise Store", coupon: "SUMMER_FUN", discount: 2.22, index: 0, item_brand: "Google", item_category: "Apparel", item_category2: "Adult", item_category3: "Shirts", item_category4: "Crew", item_category5: "Short sleeve", item_list_id: "related_products", item_list_name: "Related Products", item_variant: "green", location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo", price: 9.99, quantity: 1, }, ]; ``` The items array is made of objects. In our example, the items array is only made of 1 object. Going over the item parameters, let’s take a look at the item_variant as well as item_category2. Using these 2 parameters, we can build a chart that helps us answer the question above. > Note: you can pass whatever value you deem appropriate. It is recommended that you make of use of the item_category through item_category5 to send as much context as possible about the items involved in users’ actions. Back to our regular programming. With the event parameters, all we could read is a user added 7.77 US dollars worth of our products to the cart. Now, we can read way more; way way more. Using the two parameters we chose earlier (item_category and item_category5), we can read a user added a short sleeve worth 7.77 US dollars from our apparel products to their cart. Now things are much clearer. We can read into the data better if we replace the item_category with item_category2 and we add the discount parameter. Using this data, we can read a user added an adult short sleeve worth 9.99 US dollars to their cart and applied a 2.22 discount. This sentence clearly describes the user behaviour. Hopefully, the example above highlighted the difference between event parameters and item parameter and the power of combining this contextual data to draw clear pictures of user behaviour. ## Tracking non checkout events To make things easier for everyone, E-commerce events are going to be categorized into non checkout events and checkout events. If you are interesting in tracking the checkout behaviour including refunds, please skip to that section. ### add_to_cart This is one of the most commonly tracked events for online store. So, let’s learn how to track it. The most common way to add a product to the cart is to use the add to cart button present on every product detail page. So, the best way to track this would be add use the addEventListener method. In short, we want that every time a user tracks the add to cart button, we trigger the add_to_cart event. For demonstration purposes, we will use a simple product detail page. If your implementation is much more complex, the implementation is the same. You will need to adjust the code a little bit but the concept is the same. Let’s dig in. The first step is to make sure that we are able to properly identify the Add To Cart button. Without this button, our efforts will be in vain. The next step is to make sure we have accessible elements on the page that clearly indicated: - quantity - product’s name - any other metadata to be sent with the add to cart event > Please make sure that this information is clearly accessible throughout all the product details pages. Ideally, you can use the same CSS selectors to access the information above. Now that we know that we can access the data, let’s understand the procedure of the implementation: - Once the user clicks on the Add To Cart button, we will fire an add_to_cart event using the addEventListener method - The function to be executed will send the event to Google Analytics 4 Let’s have a look at the code that will do what we just described: ```js // Sending an add to cart event every time the user click on the Add to Cart button const addToCartButton = document.querySelector('a.round-black-btn[href="#"]'); const regexNum = "[+-]?([0-9]*[.|,])?[0-9]+"; //Regular Expression to extract numbers from a string const getProductPrice = () => { let stringProductPrice = document.querySelector(".product-price").textContent; // Getting the price as a string let numbersOnlyStringProductPrice = stringProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let productPrice = Number(numbersOnlyStringProductPrice); // Changing the price's data type into a number return productPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called }; const getCartValue = () => { let quantityAdded = document.querySelector('input[name="quantity"]').value; let productUnitPrice = getProductPrice(); let cartValue = productUnitPrice * quantityAdded; return cartValue; // The return statement ensures that cartValue is the output of the getCartValue every time the function is called }; // Defining the add to cart function const addToCart = () => { gtag("event", "add_to_cart", { currency: "USD", value: getCartValue(), // Using the function getCartValue to calculate the cart's total value items: [ { item_name: document.querySelector("div.product-name").textContent, price: getProductPrice(), // Using the getProductPrice function to fetch the product's unitary price quantity: document.querySelector('input[name="quantity"]').value, }, ], }); }; addToCartButton.addEventListener("click", addToCart); // Every time the addToCartButton is clicked, the addToCart function will be called. ``` This code block adds an event listener to track the "add to cart" event in Google Analytics 4. When a user clicks the Add to Cart button, the code runs the function called addToCart. The "addToCart" function sends data to Google Analytics 4 via the gtag function. The data sent to Google Analytics 4 includes the currency code, the total value of the cart, and the details of the item(s) in the cart (e.g. item name, price, and quantity). The values for the cart's total value and the details of the item(s) in the cart are calculated by calling two functions, getCartValue and getProductPrice, respectively. The "getCartValue" function calculates the total value of the cart by multiplying the quantity of the item selected by the user by the product's unit price. The "getProductPrice" function returns the unit price of the product. > Please note that you can add error handling to this code block as well as more logic to make sure the price can always be returned even if it is being displayed in other formats such as 39,00. The code block is for demonstration purposes only and should only be used for production if it has been approved by your developers. Now that we are sending the add_to_cart event to Google Analytics 4, let’s take a minute to discuss the items array. This is an important part of E-commerce tracking, so it is important to make sure that it is well understood. The items array is required to for every E-commerce event. Please note that it does not mean that it needs to be populated. If the items array is null, the event is still valid. The items array is composed of one or more object with each object describing a specific product. Google’s documentation specifies which keys can be passed within the object. Here’s the exhaustive list: | Name | Type | Required | Example value | Description | | ---------------- | ------ | -------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `item_id` | string | Yes\* | SKU_12345 | The ID of the item. \*One of `item_id` or `item_name` is required. | | `item_name` | string | Yes\* | Stan and Friends Tee | The name of the item. \*One of `item_id` or `item_name` is required. | | `affiliation` | string | No | Google Store | A product affiliation to designate a supplying company or brick and mortar store location. Note: `affiliation` is only available at the item-scope. | | `coupon` | string | No | SUMMER_FUN | The coupon name/code associated with the item. Event-level and item-level `coupon` parameters are independent. | | `discount` | number | No | 2.22 | The monetary discount value associated with the item. | | `index` | number | No | 5 | The index/position of the item in a list. | | `item_brand` | string | No | Google | The brand of the item. | | `item_category` | string | No | Apparel | The category of the item. If used as part of a category hierarchy or taxonomy then this will be the first category. | | `item_category2` | string | No | Adult | The second category hierarchy or additional taxonomy for the item. | | `item_category3` | string | No | Shirts | The third category hierarchy or additional taxonomy for the item. | | `item_category4` | string | No | Crew | The fourth category hierarchy or additional taxonomy for the item. | | `item_category5` | string | No | Short sleeve | The fifth category hierarchy or additional taxonomy for the item. | | `item_list_id` | string | No | related_products | The ID of the list in which the item was presented to the user. If set, event-level `item_list_id` is ignored. If not set, event-level `item_list_id` is used, if present. | | `item_list_name` | string | No | Related products | The name of the list in which the item was presented to the user. If set, event-level `item_list_name` is ignored. If not set, event-level `item_list_name` is used, if present. | | `item_variant` | string | No | green | The item variant or unique code or description for additional item details/options. | | `location_id` | string | No | ChIJIQBpAG2ahYAR_6128GcTUEo (the Google Place ID for San Francisco) | The physical location associated with the item (e.g. the physical store location). It's recommended to use the [Google Place ID](https://developers.google.com/maps/documentation/places/web-service/place-id) that corresponds to the associated item. A custom location ID can also be used. Note: `location id` is only available at the item-scope. | | `price` | number | No | 9.99 | The monetary price of the item, in units of the specified currency parameter. | | `quantity` | number | No | 1 | Item quantity. If not set, `quantity` is set to 1. | These item parameters are the same for all E-commerce events. Just make sure your events have the same parameters to create continuity for your tracking. ### add_to_wishlist Since we have our add_to_cart event properly implemented, we can use the same logic to track the rest of the events. Let’s have a look at the add_to_wishlist event. Here are some things to consider when working with this event: - The parameter will not be used with this event since we adding a singular product to the wish list - The value of the event is equal to the unitary value of the product Let’s have a look at the code block we will be using to track this event: ```js // Sending an add to wish list event every time the user click on the Add to Wish List button const addToWishListButton = document.querySelector('a.round-black-btn.wishlist[href="#"]'); // Defining the add to cart function const addToWishList = () => { gtag({ 'event', 'add_to_wishlist', // Event name. Do not change unless necessary { 'currency': "USD", 'value': getCartValue(), // Using the function getCartValue to calculate the cart's total value 'items': [ { item_name: document.querySelector('div.product-name').textContent, price: getProductPrice(), // Using the getProductPrice function to fetch the product's unitary price } ] } }); }; addToWishListButton.addEventListener('click', addToWishList); // Every time the addToWishList is clicked, the addToWishList function will be called. ``` The purpose of the code block is to track the "add to wish list" event in Google Analytics 4. It starts by selecting the add to wish list button on the page with the line const addToWishListButton = document.querySelector('a.round-black-btn.wishlist[href="#"]');. Next, it defines the addToWishList function which will be used to trigger the tracking event. This function sends an event to Google Analytics 4 with the event name "add_to_wishlist". The event contains the currency, value and item details. The currency is hardcoded as USD and the value is calculated using the getCartValue function. The item details are extracted from the page by selecting the product name and price using document.querySelector and the getProductPrice function. Finally, the code block adds an event listener to the add to wish list button. The event listener listens for clicks on the add to wish list button and calls the addToWishList function when the button is clicked. This means that every time the user clicks the add to wish list button, the event will be triggered and sent to Google Analytics 4. ### generate_lead This event, while not an E-commerce event, is on the recommended events list. You can use this event to understand how your different newsletters are growing. Essentially, every time a user signs up to a newsletter, we will generate this event and add the newsletter’s title in order for us to be more informed about which newsletters are driving the most leads. Since we are going to be working with form submissions, we will be listening for the submission of the form and we will be sending the event accordingly. Please not that we are not going to do any client-side validation for the form. If you want to read more about said topic, follow this [link](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation). Let’s have a look at the code we will be using to track the generate_lead event. ```js // Identifying the newsletter form const newsletter_form = document.querySelector("form#product_releases"); const newsletter_name = newsletter_form.getAttribute("id"); // Sending the generate_lead event every time the newsletter form is submitted const generateLead = (e) => { e.preventDefault(); // This is to prevent the default behaviour of the submit event gtag("event", "generate_lead", { newsletter_name: newsletter_name, }); }; newsletter_form.addEventListener("submit", generateLead); ``` The first part of the code identifies the newsletter form by its ID and assigns it to the variable newsletter_form. It then gets the ID of the form and assigns it to the variable newsletter_name. The second part of the code defines a function generateLead, which sends an event called generate_lead to Google Analytics 4 every time the newsletter form is submitted. The event will also include the newsletter_name as a parameter. Finally, the code adds an event listener to the newsletter form, which listens for the submit event. When the form is submitted, the generateLead function is called and the event is sent to Google Analytics 4. ### select_item As defined by the list above, this event should fire whenever a use selects an item from a list. To put it in other terms, whenever a user clicks on an item to view from a given list, the event needs to be triggered and the appropriate data needs to be sent with it. So, the goal is to fire the select_item event whenever the user clicks on a product card. Here's how can achieve this goal. ```js // Getting the list of related products const relatedProducts = document.querySelectorAll('a[href=""].card'); // itemArray let itemArray = []; // We will use this array to send the data into the items array inside the dataLayer event // selectItem function const selectItem = (e) => { itemArray.push({ item_name: e.target.closest("a.card").querySelector("h5.card-title") .textContent, price: (function () { let stringRelatedProductPrice = document.querySelector(".product-price").textContent; // Getting the price as a string let numbersOnlyStringRelatedProductPrice = stringRelatedProductPrice.match(regexNum)[0]; // Extracting the numbers from the price string let RelatedproductPrice = Number(numbersOnlyStringRelatedProductPrice); // Changing the price's data type into a number return RelatedproductPrice; // The return statement ensures that productPrice is the output of the getProductPrice every time the function is called })(), }); gtag("event", "select_item", { item_list_id: "related_products", item_list_name: "Related products", items: itemArray, }); }; ``` The code first selects all the related products by using document.querySelectorAll('a[href=""].card') and storing it in the relatedProducts variable. Then an empty array itemArray is declared which will be used to store the selected item's data. The selectItem function is then defined, which is called when an item is selected. The function pushes the selected item's data (item name and price) into the itemArray array. The price of the selected item is calculated using a self-invoking function, which first extracts the string representation of the price, then removes any non-numeric characters, converts the remaining string to a number, and finally returns it. Finally, the gtag function is called with the 'event', 'select_item', and the data regarding the selected item list and its items. The item_list_id and item_list_name are set to "related_products" and "Related products" respectively. The items property is set to the itemArray which contains the data of all the selected items. > Please note that regexNum has been declared above. If you haven't declared this variable, do so before executing this block of code ### view_cart This is one of the new E-commerce events introduced in Google Analytics 4. This event will help you to know how many customers are actually visiting the cart. The event can be used to gauge the propensity to purchase. Most of E-commerce websites have side carts that open when the user wants to look at what they have in the cart. Maybe users want to know the value of the cart or to add/remove some items. Either way, we are going to use this event to make sure that whenever the cart shows, we are able to track it. Another scenario we will look at is when users are taken to a cart page (/cart). Let’s dig into the first scenario where users will interact with a side cart. Before we dive into the code block and its different section, note that we are using Bootstrap 5 to mimic the “side cart”. In this particular case, we will be using the Offcanvas component. This important for you to know since we will be using the JavaScript events of this component to trigger the view_cart event. You can read more the component and its event [here](https://getbootstrap.com/docs/5.0/components/offcanvas/#events). If your implementation is different, the premise is the same, fire the event when the side cart is fully shown to the user. ```js let sideCartProductsArray = []; let sideCartValue = 0; const viewCart = () => { let sideCartProducts = document.querySelectorAll( "div.side-cart li.list-group-item", ); if (sideCartProducts) { sideCartProducts.forEach((sideCartProduct) => { sideCartProductsArray.push({ item_name: sideCartProduct.querySelector(".product-title").textContent, price: (function () { let sideCartStringProductPrice = sideCartProduct.querySelector(".product-price").textContent; let sideCartNumbersOnlyStringProductPrice = sideCartStringProductPrice.match(regexNum)[0]; let sideCartProductPrice = Number( sideCartNumbersOnlyStringProductPrice, ); return sideCartProductPrice; })(), }); }); for ( let sideCartProductValue = 0; sideCartProductValue < sideCartProductsArray.length; sideCartProductValue++ ) { sideCartValue += sideCartProductsArray[sideCartProductValue].price; } gtag("event", "view_cart", { currency: "USD", value: sideCartValue, items: sideCartProductsArray, }); } }; // Getting the side cart and firing the event whenever the side cart is shown let sideCart = document.getElementById("offcanvasExample"); sideCart.addEventListener("shown.bs.offcanvas", () => { console.log("Cart viewed"); viewCart(); }); ``` The code block listens to an event triggered by the showing of a side cart element with the id of "offcanvasExample". When the event is fired, the function viewCart() is called and performs the following steps: 1. The code retrieves all the products in the side cart using the querySelectorAll method and saves it in the sideCartProducts variable. 2. The code uses the forEach method to loop through the sideCartProducts and push each product's item_name and price into the sideCartProductsArray. The price of the product is obtained by calling a function that retrieves the price as a string, extracts the numbers from the string, and converts the numbers into a number data type. 3. The code calculates the total value of all the products in the sideCartProductsArray by adding the price of each product to the sideCartValue variable. 4. The code then uses the gtag function to trigger an event named "view_cart" and pass the currency, value, and items as parameters. Finally, the code adds an event listener to the side cart element that listens for the "shown.bs.offcanvas" event. When this event is fired, the viewCart function is called and the event is triggered. That’s it for the first scenario where a side cart is shown to the user. This way of tracking is more complicated then on a page view. If the you want to fire the same event using the /cart page load as the trigger, you can do so by modifying the trigger from the shown.bs.offcanvas to load. This is admitting the the same CSS selectors are being used on the /cart page. If not then simply modify the CSS selectors to make sure you are getting what you need. ### view_item If you are still having trouble working with triggering an event on page load, you are in luck! The view_item event, as described above, is an event that will fire the whenever a user views an item. To make things clearer, we want to fire this event whenever a user visit a product details page. With this in mind, we should be able to track this event with relative ease. Here’s what the code will look like: ```js // Defining the add to cart function const viewItem = () => { gtag("event", "view_item", { currency: "USD", value: getProductPrice(), items: [ { item_name: document.querySelector("div.product-name").textContent, price: getProductPrice(), }, ], }); }; window.addEventListener("load", viewItem); ``` The code block is defining the viewItem function which fires an event in Google Analytics through the gtag function. The purpose of this event is to track when a user views a particular item on the website. The function uses the gtag function to fire an event with the following parameters: - The first parameter is the event action - 'view_item' - The second parameter is an object that contains the following details about the event: - 'currency': the currency used is set to "USD". - 'value': the value of the item being viewed is obtained from the getProductPrice() function. - 'items': an array of objects that contains the name of the item and its price. The viewItem function is executed once the entire window is loaded through the window.addEventListener function which listens for the load event. ### view_item_list This is one of the trickiest events to work with. First, based on the definition, we can fire the event when the user is on the collections page or when they have viewed a recommended list for related products or for the recently viewed products. For this to work, we will be using the IntersectionObserver API. If you are not familiar it, have a [read](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). The goal of this procedure is to fire the view_item_list whenever the recommended products list enters into view. Let’s have a look at the code. ```js // creating the observer for the recommended products list let options = { threshold: 1.0, }; // creating observedProductsArray. This will be the value of the items array in the dataLayer let observedProductsArray = []; const hasBeenSeen = (entries) => { if (entries) { // Firing this event only when we are sure that the intersection happened let observedProductsList = document.querySelectorAll( "div.recommended-products a.card", ); // List of observed products observedProductsList.forEach((observedProduct) => { observedProductsArray.push({ item_name: observedProduct.querySelector("h5").textContent, price: (function () { let ObservedStringProductPrice = observedProduct.querySelector(".product-price").textContent; let numbersOnlyStringObservedProductPrice = ObservedStringProductPrice.match(regexNum)[0]; let observedProductPrice = Number( numbersOnlyStringObservedProductPrice, ); return observedProductPrice; })(), }); }); gtag("event", "view_item_list", { item_list_id: entries[0].target.getAttribute("data-list-id"), // entries is an object item_list_name: entries[0].target.getAttribute("data-list-name"), items: observedProductsArray, }); } observer.unobserve( document.querySelector("div.recommended-products div.card-group"), ); // Unobserving the element so the callback function will only get called once }; let observer = new IntersectionObserver(hasBeenSeen, options); observer.observe( document.querySelector("div.recommended-products div.card-group"), ); ``` This code block creates an IntersectionObserver for the recommended products list. The observer will track the intersection of the recommended products list with the viewport of the website. When the recommended products list intersects with the viewport, the observer will call the "hasBeenSeen" function. The "hasBeenSeen" function creates an array of observed products called "observedProductsArray". It populates this array with the names and prices of each product in the recommended products list by using the querySelectorAll method to select the individual products and their associated information. Once the "observedProductsArray" is populated, the "hasBeenSeen" function triggers a "view_item_list" event in the Google Analytics tracking code (gtag). This event includes information about the list, such as the list ID and name, as well as the list of observed products contained in "observedProductsArray". Finally, the code unobserves the recommended products list after the event has been fired so that the callback function will only be executed once. Hopefully this explanation, however brief, helps you understand the code better. Now, let’s talk about the E-commerce pages with multiple lists. If you have more than one product list, then it is recommended to add multiple observer to the right lists to avoid complicated the tracking code. If you are comfortable working with loops that you can create a loop to iterate over all lists that you want to target and use the loop to create the Intersection Observers and track the lists whenever they are 100% in the users’ view port. ## Tracking checkout events Now that everything is set prior to checkout, the next step is to focus on the checkout events which are begin_checkout, add_payment_info, add_shipping_info, and purchase. The first event we will start with is begin_checkout. ### begin_checkout This event is very straightforward. The goal is to send an event to Google Analytics 4 every time a user begins a checkout. If you are wandering if the event is going to fire for every occurrence, then the answer is yes. The reason for that is that it reflects the actual behaviour of your users. Let’s dig in. ```js let checkoutProductsArray = []; let checkoutProductsValue = 0; const beginCheckout = () => { let checkoutProducts = document.querySelectorAll( "div.side-cart li.list-group-item", ); if (checkoutProducts) { checkoutProducts.forEach((checkoutProduct) => { checkoutProductsArray.push({ item_name: checkoutProduct.querySelector(".product-title").textContent, price: (function () { let checkoutStringProductPrice = checkoutProduct.querySelector(".product-price").textContent; let checkoutNumbersOnlyStringProductPrice = checkoutStringProductPrice.match(regexNum)[0]; let checkoutProductPrice = Number( checkoutNumbersOnlyStringProductPrice, ); return checkoutProductPrice; })(), }); }); for ( let beginCheckoutProductValue = 0; beginCheckoutProductValue < checkoutProducts.length; beginCheckoutProductValue++ ) { checkoutProductsValue += checkoutProductsArray[beginCheckoutProductValue].price; } gtag("event", "begin_checkout", { currency: "USD", value: checkoutProductsValue, items: checkoutProductsArray, }); } }; ``` This code block is defining a function called beginCheckout that will be fired when the user begins the checkout process. The function uses the querySelectorAll method to select all of the items in the user's cart and store them in an array called checkoutProductsArray. The function then loops through each item in the array, using a regular expression to extract the price of each item, and adds it to a running total of the total value of all items in the cart. Finally, the function uses the gtag function to fire a Google Analytics event called begin_checkout and pass in the currency (USD), total value, and the items in the cart as event parameters. ### add_payment_information & add_shipping_info These steps, if tracked, will help you establish a checkout funnel that you can use in order to gain insights in your checkout abandonment and build cart-recovery programs that will help you increase your revenue. However, to track these events outside of platform that offer direct integration with Google Analytics 4 or Shopify Plus, it is recommended to work with a developer to get it done. The reason is simple. These events need to be triggered once a user successfully ads their payment information and successfully ads their shipping information. In order to properly trigger these events, you will probably be using API response codes that indicate that payment information is valid or that the submitted address exists. That being said, it’s better to work with a developer to get these events tracked right. Here are some resources about the events: - [add_shipping_info](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#add_shipping_info) - [add_payment_info](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#add_payment_info) ### purchase This is perhaps one of the most important E-commerce events you would want to track. That being said, it is also better to work with developers to track this event if you are not using an an E-commerce that is integrated with Google Analytics 4 or something like Shopify where we can insert tracking codes easily. Here’s the documentation for the [purchase](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#purchase) event. There are somethings you would want to consider before moving to the implementation: - You want the event to fire after you have received an API response that the payment has been successfully processed and not when the purchase button is clicked - If you firing the event based on a thank you page, please make sure to the fire the event on first access and not on every page load - Make sure that the items involved in the transaction are available to be used wherever you are trying to implement the purchase event That's it folks! If you managed to successfully follow all of the steps mentioned in this tutorial, you should have a functioning E-commerce tracking using gtag.js

Tracking Hubspot Meeting Bookings with Google Tag Manager

May 6, 2024

In a previous post, we have covered how you can track different Hubspot form interactions, from loading to submission. In this post, we will cover one of the most used features of Hubpost being. their free meeting scheduler. Whether it’s your marketing or sales team using this for leads acquisition, it is important that these forms are properly tracked to measure the success of your marketing campaigns. ## Tracking Hubspot Meeting Requests Unlike form tracking, we will only be tracking a single event which is **meetingBookSucceeded**. This is the actual event name Hubspot exposes on the window object when a meeting is successfully booked on their tool. With this event, we will able to send details to analytics tools like Google Analytics 4 that will enrich event making it easier to make informative decisions. Here are the properties we will send in the payload or in this case log into the dataLayer: - url - formID - paidMeeting - linkType - offline Here’s what the full tracking code looks like: ```js window.addEventListener("message", function (event) { if (event.data.meetingBookSucceeded) { window.dataLayer.push({ event: "hubspot_meeting_booked", url: window.location.href, formID: event.data.meetingPayload.formGuid, paidMeeting: event.data.meetingPayload.isPaidMeeting, linkType: event.data.meetingPayload.linkType, offline: event.data.meetingPayload.offline, }); } }); ``` ## Where to add the tracking code? Now that we have the tracking code logging the event hubspot_meeting_booked into the dataLayer, we have to figure where to put this code. Well, there are two ways we can do this: 1. Google Tag Manager using the Custom HTML 2. Source code ## Adding Tracking Code using Google Tag Manager To create a custom HTML tag in Google Tag Manager, follow these steps: - **Step 1: Open Google Tag Manager** - Log into your Google Tag Manager account. - Select the appropriate container for your website. - **Step 2: Create a New Tag** - Navigate to the Tags section on the left sidebar. - Click on New to start creating a new tag. - **Step 3: Configure the Tag** - Click on Tag Configuration. - Select Custom HTML as the tag type. - Enter the code above. Ensure it is enclosed in <script> tags. - **Step 4: Set up Triggering** - Click on Triggering to decide when this tag should fire. - Choose from the existing triggers or create a new one by clicking on New. - Choose the All Pages trigger - **Step 5: Save and Test Your Tag** - Click on Save to save the tag. - Enable Preview mode to test your tag on your site. - Check the GTM debug panel to ensure the tag fires correctly. ## Adding Tracking Code in the Source Code If you want to add the tracking code to your source code, please share the code above with your developer team and they will implement it. ## Next Steps The next step is to create tags to send data to different platforms such as Google Analytics 4, Facebook Ads, Google Ads and so on. ## Conclusion With this tracking code allowing to track successful meeting bookings, you should be able to measure the success of your lead generating campaigns and measure the growth of your business.

Tracking Looker Studio Dashboards

February 29, 2024

We will explore how to track pageviews across your Looker Studio reports. This insight will help you understand how your data creations are utilized and how you can support stakeholders by making dashboards more accessible. ## Tracking prerequisites Before proceeding with the tracking steps, ensure you have the following ready: - The Looker Studio report(s) you want to track. - The measurement ID from the Google Analytics 4 property where you plan to send the data. It is recommended to create a new property for this purpose. ## Step 1: Adding the measurement ID to Looker Studio Once you have your measurement ID, the first step is to add it to the Looker Studio report(s) you wish to track. To do this: 1. Open the Looker Studio report you chose. 2. Click on "File" and select "Report settings". 3. In the "Google Analytics Measurement IDs" section, paste your measurement ID. ## Step 2: Configuring the Google Analytics 4 property After adding the measurement ID to your Looker Studio report(s), the next step is to configure the destination: - **Stream URL**: Ensure the protocol is https:// and the value equals lookerstudio.google.com. - After saving the configuration, refresh your Looker Studio reports to initiate tracking. ## Verifying the implementation To check if data is flowing correctly, navigate to the Realtime report of your property. ## Reporting on Looker Studio usage data When reporting on Looker Studio usage data, consider the following: - Pay close attention to the URL. If the page path includes /edit, it reflects activity from editing the dashboard by you or your collaborators. Use the Exploration feature to filter out these pages and focus on those used by dashboard users. - For grouping pageviews per dashboard, use the RegExp (.\*?) › to create a custom field when importing data into Looker Studio. - For pageviews per page, use the RegExp › (.\*?) for creating a custom field. - To track the "Copy Report" action, especially for public templates, create a custom event in Google Analytics 4 with the event name form submit and form destination containing /copyReport. ## Conclusion By following the steps outlined in this blog, you'll be able to track your Looker Studio dashboards and gain insights into how stakeholders use the insights presented. This data is invaluable for understanding how to improve data adoption within your organization.

Tracking Thinkific Events

February 13, 2023

Thinkific is one of the most commonly used platforms by creators to create their courses, monetize memberships and grow their online audience. As a creator, understanding your growth trends is crucial for your business and that's why today, we will talking about how you can track Thginkific events in order for you to have visibility over your students' interactions with your courses. That being said, the tracking we are going to implement will be through Google Tag Manager. Let's dive right in. ## Thinkific events As any platform, Thinkific does offer a lot features for its users, but what we are interested in are the features that would help us set up our online measurement. The events we are going to review during the article are: - Signup - Purchase - Course completion - Content completion These events constitute the most important interactions with your Thinkific-based site. If more tracking needs to be done, we are going to cover how to do that, so keep on reading. Beforewe begin make sure that you have your Google Tag Manager container ready. If don't have that ready yet, follow this link to set up your container. ## Thinkific's code & analytics Once your container is set up, it's time to add its installation script to Thinkific. This is what is going to allow us to track the abovementioned events. Adding the installation code is a very straightforward task. First, proceed to copy your container's installation script. The script looks something like this: ```html <!-- Google Tag Manager --> <script> (function (w, d, s, l, i) { w[l] = w[l] || []; w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" }); var f = d.getElementsByTagName(s)[0], j = d.createElement(s), dl = l != "dataLayer" ? "&l=" + l : ""; j.async = true; j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl; f.parentNode.insertBefore(j, f); })(window, document, "script", "dataLayer", "GTM-XXXXXXX"); </script> <!-- End Google Tag Manager --> ``` You do not have to add the second section of the installation code. That part will not be necessary. The next step is to log into your Thinkific's account, click on Settings and then click on the Code & analytics tab inside the settings page. In the code & analytics section, you will see the following sections: - Site footer code - Order tracking code - Signup tracking code These are the section where we will be pasting our code to complete our events tracking. For our Google Tag Manager installation script, we will need to add it in the Site footer section ensuring that our container will load on every page which should allow us to track whatever we need. ## Tracking Thinkific Events Once our container has been added to the Site footer, we can get started with out tracking. Since we on the Site footer, let's discuss the events we can track in this section. ### Content Completed The first event we are going to track in this section is the module completed event. This event fire whenever a user click on the "Next" button and completes the lesson. To fire this event, we will be using the Course Player Event Hooks. Here's an example usage of the hooks: ```js $(function () { if (typeof CoursePlayerV2 !== "undefined") { CoursePlayerV2.on("hooks:contentWasCompleted", function (data) { data["user"] = Thinkific.current_user; ThinkificAnalytics.track("Custom Content Completed", data); }); } }); ``` That being said, here's the tracking code for firing a content completed event using the dataLayer: ```js $(function () { if (typeof CoursePlayerV2 !== "undefined") { // EVENT HOOK: Content completed CoursePlayerV2.on("hooks:contentWasCompleted", function (data) { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "Content Module Completed", }); }); } }); ``` The given code is a script written in jQuery that listens for the contentWasCompleted event fired by the CoursePlayerV2 object. If the CoursePlayerV2 object is defined, the script will push an event named "Content Module Completed" to the dataLayer when the contentWasCompleted event is fired. The dataLayer is a global JavaScript object used to store data that is passed to Google Tag Manager for tracking purposes. If your purpose is just to track the event then you are done. If you are interested in sending parameters along with the event then let's do just that. Based on Thinkific's documentation, we can see the event passes the following data: ```js var data = { lesson: {} //object containing lesson attributes, chapter: {} //object containing chapter attributes, course: {} //object containing course attributes, enrollment: {} //object containing enrollment attributes, user: {} //object containing student attributes } ``` Here's what the content complete event would like with a full list of parameters being sent to the dataLayer: ```js // Event = Completed Module and Course $(function () { if (typeof CoursePlayerV2 !== "undefined") { // EVENT HOOK: Content completed CoursePlayerV2.on("hooks:contentWasCompleted", function (data) { console.log("Content Module Completed", data); //Optional this is for debugging window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "content_completed", //Lesson metadata lesson_name: data.lesson.name, lesson_slug: data.lesson.slug, lesson_position: data.lesson.position, lesson_chapter_id: data.lesson.chapter_id, lesson_id: data.lesson.id, //Chapter metadata chapter_name: data.chapter.name, chapter_course_id: data.chapter.course_id, chapter_id: data.chapter.id, //Course metadata course_name: data.course.name, course_slug: data.course.slug, course_landing_page_url: data.course.landing_page_url, course_video_completion_percentage: data.course.video_completion_percentage, course_product_id: data.course.product_id, course_id: data.course.id, //Enrollment metadata enrollment_percentage_completed: data.enrollment.percentage_completed, enrollment_activated_at: data.enrollment.activated_at, enrollment_created_at: data.enrollment.created_at, enrollment_date_started: data.enrollment.date_started, enrollment_course_id: data.encrollment.course_id, enrollment_id: data.enrollment.id, //User metadata user_id: data.user.id, user_created_at: data.user.created_at, user_last_sign_in_at: data.user.last_sign_in_at, user_number_courses_complete: data.user.number_courses_complete, }); }); } }); ``` You can use this data to enrich your tracking about user behaviour. For instance, using this metadata, you can know the % of your content users are completing after purchasing your courses. Once you have made your choice about adding the parameters, all you have to do is add the right code block into the footer section and you are done. ### Course Completed Tracking this event is exactly like tracking the content completed event. Here's the event will its full list of parameters: ```js // Event = Completed Module and Course $(function () { if (typeof CoursePlayerV2 !== "undefined") { // EVENT HOOK: Content completed CoursePlayerV2.on("hooks:enrollmentWasCompleted", function (data) { console.log("Content Module Completed", data); //Optional this is for debugging window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "course_completed", //Lesson metadata lesson_name: data.lesson.name, lesson_slug: data.lesson.slug, lesson_position: data.lesson.position, lesson_chapter_id: data.lesson.chapter_id, lesson_id: data.lesson.id, //Chapter metadata chapter_name: data.chapter.name, chapter_course_id: data.chapter.course_id, chapter_id: data.chapter.id, //Course metadata course_name: data.course.name, course_slug: data.course.slug, course_landing_page_url: data.course.landing_page_url, course_video_completion_percentage: data.course.video_completion_percentage, course_product_id: data.course.product_id, course_id: data.course.id, //Enrollment metadata enrollment_percentage_completed: data.enrollment.percentage_completed, enrollment_activated_at: data.enrollment.activated_at, enrollment_created_at: data.enrollment.created_at, enrollment_date_started: data.enrollment.date_started, enrollment_course_id: data.encrollment.course_id, enrollment_id: data.enrollment.id, //User metadata user_id: data.user.id, user_created_at: data.user.created_at, user_last_sign_in_at: data.user.last_sign_in_at, user_number_courses_complete: data.user.number_courses_complete, }); }); } }); ``` If you want to track course completions and send the full list of parameters then simply add the script to the site footer and you will be good to go. If you do not need the parameters, simply delete them before pasting the event into the Site footer. ### Signups For this section of tracking, we are going to be adding our tracking code to the Signup tracking code section. The signup tracking code is triggered when a user successfully signs up. Let's take a look at the tracking code for this event: ```js window.dataLayer = window.dataLayer || []; dataLayer.push({ event: "sign_up", }); ``` If you need to send parameters with the event, the following variables can be used: - id - first_name - last_name - full_name - email - create_at To use the variable, used the following syntax {{variable_name}}. Next up is the purchase event. ### Purchase To track the purchase event, we will be adding our code to the Order tracking section. This code will run on the thank you page when a purchase is completed. Let's have a look at the tracking code: ```js window.dataLayer = window.dataLayer || []; dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object. dataLayer.push({ event: "purchase", ecommerce: { transaction_id: '{{ order_number }}', value: {{total_price}}, currency: 'USD', items: [{ item_name: '{{ product_name }}', quantity: 1, price: {{total_price}}, currency: 'USD', affiliation: '{{referral_code}}' }] } }); ``` When a purchase is made, this code generates a "purchase" event with details of the transaction. The first two lines of the code block initialize the window.dataLayer array and clear any previous ecommerce data from the data layer. The dataLayer.push() function is then called to push a new event to the data layer with the event name of "purchase". The ecommerce object nested within the event contains details about the transaction, including the transaction ID, transaction value, and currency. The items array within the ecommerce object contains information about the specific products in the transaction, including the product name, quantity, and price. The affiliation field can be used to identify the referral code that led to the purchase. If you need more parameters sent, here's the full list: - order_number - order_name - billing_name - billing_email - payment_method - total_price_in_cents - total_price - referral_code - product_id - payment_provider - buyer_identified > If you are using these variables, please wrap them up inside {{ }} ## Tags Configuration in Google Tag Manager After you have added the tracking codes to the right tracking sections, it is time to configure your tags in Google Tag Manager. When configuring the Google Analytics 4 event tags, use the {{Event}} variable for the event name. For the purchase event, enable the Send Ecommerce data setting under the More Settings dropdown. If you are using any parameters, make sure to create dataLayer variables in Google Tag Manager. That's wrap folks! At this point, you should be able to track your Thinkific events using Google Tag Manager and the dataLayer.

Tracking Unbounce Form Submissions with Google Tag Manager

March 3, 2024

Unbounce is one of the most commonly used landing page builders for teams across the globe, and for good reason. If you are working with Unbounce, in this tutorial will walk through how you can track successful form submissions using Google Tag Manager and send that data to Google Analytics 4, Google Ads or any other destination. ## Pre-requisites Before we get into the nitty gritty, make sure you have an active Unbounce account, and deployed Google Tag Manager container. Familiarity with JavaScript will be a plus for this tutorial. Following the instructions in this tutorial will get you will successfully track form submissions, however if you need to do any changes, knowledge of web development is required. ## Unbounce Window Object In order to track successful form submission, we will be using two methods being `beforeFormSubmit` and `afterFromSubmit`. Here's the definition for each: - beforeFormSubmit: Called after validation has passed, but before the lead has been recorded. - afterFormSubmit: Called after the lead has been recorded. beforeFormSubmit and afterFormSubmit are arrays accessible through the hooks object of window.ub, Unbounce's window object. To confirm you can access the arrays, you can type the following in your browser's console: ```js window.ub.hooks; ``` When you press enter, this is the output you will get: ```js window.ub.hooks; Object { beforeFormSubmit: [], afterFormSubmit: (1) […] } ``` For this tutorial, we will be working with the **afterFormSubmit** array. The idea is to track form submissions and send this data to our target destinations and we want to do that after Unbounce has created the lead record. You can also use **beforeFormSubmit** if you choose to. Both will work just fine. ## Tracking Code Now that we know which method we are going with it is time to write our code which will capture the data we need. ```html <script> window.ub.hooks.afterFormSubmit.push(function () { window.dataLayer = window.dataLayer || []; dataLayer.push({ event: "unbounce_successful_form_submission", }); }); </script> ``` This script, when executed will log the event unbounce_successful_form_submission to the dataLayer. This event will later be used as a Custom Event trigger for to send the conversion event to Google Analytics 4, Google Ads and so on. With this script, you should be able to successfully track your form submissions. However, there are no details in this event which enables us to do further analysis. So, let's add more details allowing us to do advanced analysis on our form submission event. For instance, we can add metadata to the event to answer the following questions: 1. How many conversion each landing page variant has? 2. What is the percentage of conversions that are main goals? 3. Which are the top conversion landing pages? 4. Which users had their conversion uncaptured? Let's take a look at the new enhanced version of our event: ```js window.ub.hooks.afterFormSubmit.push(function (args) { window.dataLayer = window.dataLayer || []; dataLayer.push({ event: "unbounce_successful_form_submission", submission_details: { page_variant: window.ub.page.variantId, page_name: window.ub.page.name, isConversionGoal: args.isConversionGoal, visitor_id: window.ub.visitorId, }, }); }); ``` When this code executes, whenever a form is submitted, we will get the following details, for example: - page_variant: l - page_name: Sign up to my free course - isConversionGoal: true - visitor_id: d155ca98-3ccb-443a-a92a-a7c74dc3af18 These details can be pass then to your Google Analytics 4 event in order to enable advanced analysis for the form submit event. Do not forget to add page_variant, page_name, isConversionGoal as well as the visitor_id as custom event parameters in order to be able to use the in your reports, whether in the Explore section or Looker Studio. If you are wondering how you can check which users had their conversions uncaptured, the answer lies in data export and cross-referencing. Firstly, head to Unbounce and export the list of leads for the specific time period you are interesting in investigating. The next step would be to build a report breaking down the form submission event by visitor ID. The last and final step is to cross reference the data and whoever is missing from the form submission report is a user that had you failed at capturing their submission data. That being said, please **do not** set the visitor_id as the user_id in Google Analytics 4 as this parameter is not eligible. ## Conclusion In summary, this tutorial equips you with the necessary steps and code to effectively track form submissions from Unbounce using Google Tag Manager, enriching your data collection capabilities for Google Analytics 4, Google Ads, and other platforms. By following the instructions provided, you'll not only be able to monitor basic form submissions but also gather detailed insights for advanced analysis, enhancing your understanding of user behavior and conversion dynamics. It's crucial, however, to remain mindful of privacy and consent practices throughout this process to ensure compliance with data protection regulations.

Typeform Google Ads Conversion Tracking Complete Guide

May 20, 2023

In this article, we will be discussing the how to add Google Ads Conversion Tracking for your successful Typeform submissions. To follow along, you will need: - Your Google Ads Conversion ID - Your Google Ads Conversion Label - Google Tag Manager account - Code editor Make sure you have this information at hand before carrying on with the rest of the tutorial. If you have everything ready, let's proceed. ## Tracking Successful Typeform Submission Before we can send the conversion data to Google Ads, you need to determine how your Typeform is embedded in your website. There two main methods to embed a Typeform in a web page the, first being through HTML embedding and the second being through Vanilla JavaScript embedding. There is also a way to embed the form through React, but that is beyond the scope of this article. If you need to learn more about this method, you can do so here. ## Tracking for the HTML Embed Method HTML embedding is the easiest form to get your Typeform up and running. This form of embedding uses the CDN and it looks something like this: ```html <button data-tf-popup="<form-id>" data-tf-on-submit="submit" data-tf-opacity="100" data-tf-size="100" data-tf-iframe-props="title=Political Poll (copy)" data-tf-transitive-search-params data-tf-medium="snippet" > Try me! </button> <script src="//embed.typeform.com/next/embed.js"></script> ``` If this how you are adding a Typeform to your website then use the following code snippet to track successful submissions: ```html <script> window.dataLayer = window.dataLayer || []; // this function needs to be available on global scope (window) function submit(formId) { window.dataLayer.push({ event: "form_submit", form_id: `${formId}`, }); } </script> ``` Here's what everything looks like together: ```html <button data-tf-popup="<form-id>" data-tf-on-submit="submit" data-tf-opacity="100" data-tf-size="100" data-tf-iframe-props="title=Google Ads Conversion Tracking with Typeform" data-tf-transitive-search-params data-tf-medium="snippet" > Try me! </button> <script src="//embed.typeform.com/next/embed.js"></script> <script> window.dataLayer = window.dataLayer || []; // this function needs to be available on global scope (window) function submit({ formId }) { window.dataLayer.push({ event: "form_submit", form_id: `${formId}`, }); } </script> ``` Essentially, what we are doing is invoking the submit function every time the form is successfully submitted. When the function is invoked, it pushes a new object to the dataLayer. This new object contains the event name which is form_submit and the the form_id which reflects the id of the form being submitted. The event name form_submit is going to be used to create the trigger for our Google Ads conversion in Google Tag Manager. Now that we have the code snippet implemented, the next step is to test the code and make sure we are seeing the form_submit event being logged into the dataLayer, so we can proceed with the next step. For testing, you can paste the above code snippet in JSFiddle and simply replace <from-id> with your form id and you should be good to go. Once you are done testing, the next step is to implement the code snippet in your code base. If you do not have access to the code base, do not worry. You can still deploy this solution using the custom HTML tag in Google Tag Manager. You can so following these steps: 1. Sign in to your Google Tag Manager account: Go to the Google Tag Manager website and log in using your Google account credentials. 2. Access your container: Once logged in, select the container in which you want to create the custom HTML tag. Containers are where you manage and deploy tags. 3. Go to "Tags" section: In the container dashboard, click on "Tags" in the left-hand sidebar to access the tags management section. 4. Create a new tag: Click on the "New" button to start creating a new tag. 5. Choose a tag configuration: In the tag creation interface, you'll need to choose the tag type. Since you want to create a custom HTML tag, select the "Custom HTML" option. 6. Configure the tag: Provide a name for the tag and specify the HTML code you want to include. Page the code snippet in the provided space. 7. Set the triggering options: For the trigger choose All Pages. You can also restrict this if you want to the script to be added for pages where you have your forms implemented. 8. Save the tag: Once you have configured the tag and its triggers, click on the "Save" button to save your changes. 9. Publish the container: After creating the tag, you'll need to publish the container to make the changes live on your website. Click on the "Submit" button in the upper-right corner and follow the prompts to publish the container. Once our tag is live, we can continue with our implementation. Before we continue, make sure that you have your Google Ads conversion ID ready, as well as the Google Ads Conversion Label ready. You can find this information in your Google Ads account under "Tools & Settings" > "Measurement" > "Conversions". Once you have everything ready, please follow these steps: 1. Sign in to your Google Tag Manager account: Go to the Google Tag Manager website and log in using your Google account credentials. 2. Access your container: Once logged in, select the container in which you want to create the Google Ads Conversion Tracking tag. 3. Go to "Tags" section: In the container dashboard, click on "Tags" in the left-hand sidebar to access the tags management section. 4. Create a new tag: Click on the "New" button to start creating a new tag. 5. Choose a tag configuration: In the tag creation interface, click on the tag configuration area and search for "Google Ads Conversion Tracking" and select it. 6. Configure the tag: Provide a name for the tag and enter your Google Ads Conversion ID and Conversion Label. You can find this information in your Google Ads account under "Tools & Settings" > "Measurement" > "Conversions". 7. Set the triggering options: For the trigger, select the Custom Event trigger and in the event name field, type format_submit. This ensures that the Google Ads conversion will fire based on the event we are logging into the dataLayer using our code snippet. 8. Set additional tag options (optional): Depending on your specific requirements, you may have additional settings available to customize the tag behavior. These can include values for revenue tracking, conversion currency, and more. 9. Save the tag: Once you have configured the tag and its triggers, click on the "Save" button to save your changes. 10. Publish the container: After creating the tag, you'll need to publish the container to make the changes live on your website. Click on the "Submit" button in the upper-right corner and follow the prompts to publish the container. Once you are done with the configuration, it is time for testing. As mentioned, please make sure to publish you Google Tag Manager. To test the implementation, all you have to do is submit your Typeform and use the Google Tag Assistant to understand if the conversion is working or not. If the conversion has fired successfully, you should see that the Google Ads Conversion Tracking tag has fired. Click on the tag and confirm that you are seeing the right conversion ID as well as the right conversion label. If everything looks good, you are done! That being said, if you want to learn how to track Typeforms embedded through Vanilla JavaScript then please continue reading. Else, happy tracking! ## Tracking for the Vanilla JavaScript Embed Method This section of the article, we will see how we can implement the same level of tracking for Typeforms embedded through vanilla JavaScript. It is important to note that the only change we will see in this section is the tracking code. All the Google Tag Manager implementation remains **the same**. That being said, the following code snippet shows how you can embed a Typeform using JavaScript. ```html <button id="button">open form</button> <script src="//embed.typeform.com/next/embed.js"></script> <link rel="stylesheet" href="//embed.typeform.com/next/css/popup.css" /> <script> const { open, close, toggle, refresh } = window.tf.createPopup("<form-id>"); document.querySelector("#button").onclick = toggle; </script> ``` Now that the Typeform is implemented on your page, the next step is to simply implement the tracking code that allows us to log the form_submit into the dataLayer, so we can fire our Google Ads Conversion Tracking code. To do that, here's the snippet we need to add: ```html <script> "use strict"; window.dataLayer = window.dataLayer || []; const { open, close, toggle, refresh } = window.tf.createPopup("<form-id>", { onSubmit: ({ formId }) => { dataLayer.push({ event: "form_submit", form_id: `${formId}`, }); }, }); document.querySelector("#button").onclick = toggle; </script> ``` Once you have implemented this code snippet, replace the <form-id> with your Tyepform's actual form ID. The next step is to test the implementation and see if you are able to see the form_submit event being logged into the dataLayer. If it is, the follow the steps mentioned above to set up your Google Ads Conversion Tracking code. And, if for some reason, you do not have access to your code base, this tracking script can also be added through Gooogle Tag Manager and we have the steps on how to do this above. That's a wrap. At this point, you should be familiar with Typeforms embed methods, the different tracking snippets and how to set up Google Ads Conversion Tracking tag on Google Tag Manager based on successful form submissions.