Tracking, Insights, Tutorials for Web Analytics

Data Journal, an independent publication by Datakyu, launched in February 2023, offers an unparalleled resource for data enthusiasts. By subscribing today, you gain full access to our content and receive email newsletters with the latest content.

Explore our blog posts to discover the variety of covered topics, and feel free to subscribe to our newsletter.


Featured

Set up Enhanced Conversions for Google Ads with Google Tag Manager Set up Enhanced Conversions for Google Ads with Google Tag Manager

09/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

Implementing Google Consent Mode with Google Tag Manager and OneTrust Implementing Google Consent Mode with Google Tag Manager and OneTrust

04/02/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.

Track E-commerce Events on Shopify Track E-commerce Events on Shopify

02/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 Typeform Forms with Google Tag Manager & Google Analytics 4 Track Typeform Forms with Google Tag Manager & Google Analytics 4

03/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 Tracking E-commerce with Google Tag Manager

02/05/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 Tracking E-commerce with gtag.js

02/06/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


Recent

Unlocking Insights with GA4 Measurement Protocol Unlocking Insights with GA4 Measurement Protocol

02/03/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 Data Visualization with Open Source Web Analytics

02/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 How to Configure Data Redaction in Google Analytics 4

10/03/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 Tracking Hubspot Form Submissions with Hubspot's Global Events and Google Tag Manager

04/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.

See all

Subscribe to Data Journal