ANALYTICS

How to track AMP Pages with Google Analytics 4


David Vallejo
Share

One of the most notorious misses on Google Analytics 4. Is the lack of AMP (Accelerated Mobile Pages) Pages tracking support. While this may not be an issue for many sites, there're some website types that really need this support (like media sites or magazines).

That's why I decided to investigate the possibilities of <amp-analytics> Component and APIs in order to try to build an AMP Native Tracking without needing to draw on some tricky methods like using the infamous iframes.

I've been testing everything I could and everything seems to be working fine. I'm open to receiving feedback from people that may end up trying this solution, which may not end up being perfect, but still is more than what we actually have.

The good news is that we've got all the needed pieces of information available to perform a fully working tracking with Google Analytics 4, including the session tracking and the needed switched to have the, first_visit, session_start, user_engagment events generated, and as an unexpected extra we'll be able to set event parameters and user properties within our events

If you feel it you could buy me some coffees to support my work, this time I'm even hosting a copy of the file myself to ease the work (which depending on the traffic may lead to some costs for me.

Issues / Errors Reporting

Please report any issues/improvements on the project page on GITHUB


What features are supported

While I may have missed something, I tried to cover all the basics for the tracking and event at some point going beyond it as you'll notice in the next lines.

The current version does enable the page_view tracking (will fire on the page load if not specified in another way) along with any other event you may want to send.

This is a list of all supported features:

  • Sessions Tracking
  • Session Engagements (&seg)
  • Sessions Count (&sct)
  • First Visits Tracking (&_fv)
  • Session Starts Tracking (&_ss)
  • AMP Cross-Domain
  • User Properties (number and string)
  • Event Parameters (number and string)
  • Engagement Time Tracking (&_et)
  • Screen Resolution
  • User's Browser Language
  • Document Title
  • Document URL
  • Document Referrer
  • Unique Pageview Id (&_p)

Setting up the tracking

Tracking Snippet Component

Using the Session Data in AMP is forbidden when using Remote Configurations, this is why we are using the googleanalytics as a base, which will at the same time help in using and managing the reading/writing of the _ga cookie.

The only thing we need to do is copy the following code in our AMP pages in order to have our Google Analytics 4 tracking in place

<amp-analytics type="googleanalytics" config="https://amp.analytics-debugger.com/ga4.json" data-credentials="include">
<script type="application/json">
{
    "vars": {
                "GA4_MEASUREMENT_ID": "G-THYNGSTER",
                "GA4_ENDPOINT_HOSTNAME": "www.google-analytics.com",
                "DEFAULT_PAGEVIEW_ENABLED": true,    
                "GOOGLE_CONSENT_ENABLED": false,
                "WEBVITALS_TRACKING": false,
                "PERFORMANCE_TIMING_TRACKING": false,
                "SEND_DOUBLECLICK_BEACON": false
    }
}
</script>
</amp-analytics>

As you may have noticed there're many configuration switches, we'll later explain them all in a deeper way, but for now, the only one you should care about is the "GA4_MEASUREMENT_ID" one. You need to add your MEASUREMENT_ID in there.

You can grab that value from Admin > Properties > Data Streams > Web and then selecting your Stream

As a small sneak-peak, this is the meaning for all the configuration switches.

Feature NameDescription
GA4_MEASUREMENT_IDYour Measurement ID, G-XXXXXXXX
GA4_ENDPOINT_HOSTNAMEOverride the default endpoint domain. In case you want to send the hits to your own server or a Server Side GTM Instance.
GOOGLE_CONSENT_ENABLEDa &gcs parameter will be added to the payloads with the current Consent Status
WEBVITALS_TRACKINGIf you enable this a webvitals event will fire 5 seconds after the page is visible
PERFORMANCE_TIMING_TRACKINGWhatever you want to push a performance_timing event including the current page load performance timings
DEFAULT_PAGEVIEW_ENABLEDIf enabled a page_view event will fire on the page load
SEND_DOUBLECLICK_BEACONSend a DC Hit
Configuration Settings

Important Note

I've put an online copy of the configuration file for those who can't host themselves, if you can get some collaboration, I suggest you to download the ga4.json from GitHub and hosting it yourself.

config="https://yourdomain.com/ga4.json"

Server Side GA4 Tracking (SGTM)

If you prefer it you can have AMP send the hits to a Server Side GTM instance. for doing this set the current

GA4_ENDPOINT_HOSTNAME: "sgtm.thyngster.com"

If you prefer sending a copy of the hits to some internal database or any other tool, the logic is pretty straightforward so I don't think it needs any explanation just, set your domain there and be sure that you enable an endpoint on the following path "/g/collect"

Consent Compliance

This Google Analytics 4 tracking solution for AMP pages, supports the integration with the consent module from AMP and also with Google Consent Mode ( allowing you to attach the consent details to the hits )

Keep reading this section if you are interested in having a Consent compliance setup in your AMP Pages.
Google Consent Activation

You can have the hits to hold the information about the current consent status. This will make the tracking compatible with the Google Consent Features. If you turn on this feature the current consent status will be reported within the current event hit, allowing Google Analytics to be more GDRP Compliant (#sigh) and at the same time allowing you to make use of the consent mode modeling when it became available in the future.

To activate this you need to set the “ENABLE_CONSENT_TRACKING” switch to true, and then a a &gcs parameter will be added to all the hits, containing the actual consent status for the browsing user.

It will hold 2 different values

ValueMeaning
G100Analytics Consent Non-Granted
G101Analytics Consent Granted

In case you want to block the tracking unless the user has implicitly given his consent you can make GA4 not to fire any hits in your AMP pages. This can be easily achieved by adding the following attribute to our amp-analytics block,

data-block-on-consent

Now our main snippet will looks closely to this:

<amp-analytics
    type="googleanalytics"
    config="https://amp.analytics-debugger.com/ga4.json"
   data-credentials="include"
   data-block-on-consent
>

Two things will happen when you add this, first one if that if the current user didn’t give his explicit consent to be tracked, no hits will be sent to Google Analytics and when the user accepts the consent, AMP will fire hits that were blocked on a first instance.

Please note, that this functionality relies on the <amp-consent> component, you need to load the right library and also setup the consent modal in the way you want. A simple example would be something like:

<script async custom-element="amp-consent" src="https://cdn.ampproject.org/v0/amp-consent-0.1.js"></script>
<script async custom-element="amp-geo" src="https://cdn.ampproject.org/v0/amp-geo-0.1.js"></script>
<amp-consent layout="nodisplay" id="consent-element">
  <script type="application/json">
    {
      "consentInstanceId": "my-consent",
      "consentRequired": "remote",
      "checkConsentHref": "https://example.com/api/check-consent",
      "promptUI": "consent-ui",
      "onUpdateHref": "https://example.com/update-consent"
    }
  </script>
  <div id="consent-ui">
    <button on="tap:consent-element.accept">Accept</button>
    <button on="tap:consent-element.reject">Reject</button>
    <button on="tap:consent-element.dismiss">Dismiss</button>
  </div>
</amp-consent>

Events Tracking

Pageviews Tracking

By default a page_view event that will fire on the page load unless you set the DEFAULT_PAGEVIEW_ENABLED to false. There may be a case where you want to personalize the page_view event name, or maybe you need to add some custom parameters to it.

If that’s your case, set the default page view to false, and then add a new trigger to fire a page_view event are your own into the init config:

"custom_pageview": {
    "enabled": true,
    "on": "visible"
    "request": "ga4Event",
    "vars": {
        "ga4_event_name": "my_customized_page_view"
    },
    "extraUrlParams": {
        "event__str_real_url": "https://www.charlesfarina.com/?origin=thyngster",
        "event__str_param_2": "meh"
    }
}

Custom Events

Remember in GA4 "everything is an event", well, I tried to set up a configuration logic that allows you to track many user interactions using the currently provided functionality in AMP API.

This is cool because we're going even be able to pass User Properties and Event Parameters to our events

AMP Events Types

By default no other events than the “page_view” are tracked. But you can use any of the AMP event types to track your users interactions.

There are some more that are not currently properly documented on the main docs, so we're not covering them. You can read the original docs here:

clickWhen there's a click on an element
hiddenWhen the page becomes hidden
ini-loadWhen the initial contents of an AMP element or AMP document have been loaded.
render-startWhen the rendering of an embedded component has started (ie : ads iframes)
scrollWhen under certain conditions when the page is scrolled
timerWhen on a regular time interval
video-*When there’s a video interaction
visibleWhen an element becomes visible
blurWhen a specified element is no longer in focus
changeWhen a specified element undergoes a state change
user-errorWhen an error occurs that is attributable to the author of the page or to software that is used in publishing the page

Event: click

"triggers": {
  "mailtos": {
    "on": "click",
    "selector": "a[href^='mailto:']",
    "request": "ga4Event",
    "vars": {
      "ga4_event_name": "outgoing_click"
    },
    "extraUrlParams": {
         "event__str_outgoing_click_type": "mailto"
    }       
  }
}

This is the main one that we'll be using, it triggers when an element is clicked, and we'll use a CSS selector to define the conditional firing.


Another typical example would be tracking the external links, we could achieve this with the following trigger:
"triggers": {
  "mailtos": {
    "on": "outgoing",
    "selector": "a[href]:not(:where([href^='#'],[href^='/']:not([href^='//']), [href='thyngster.com'], [href='analytics-debugger.com'])",
    "request": "ga4Event",
    "vars": {
      "ga4_event_name": "outgoing_click"
    },
    "extraUrlParams": {
         "event__str_outgoing_click_type": "link"
    }       
  }
}

Event: hidden

We can set a trigger when the current page is hidden ( ie: minimized, or the browser's tab is switched )
If we include the visibilitySpec , we can define some rules, for example firing it only if it has been hiding for 3 seconds, see the example below

"triggers": {
  "pageHidden": {
    "on": "hidden",
    "request": "ga4Event",
    "vars": {
      "ga4_event_name": "page_is_hidden"
    },
    "visibilitySpec": {
      "selector": "body",
      "visiblePercentageMin": 20,
      "totalTimeMin": 3000
    }
  }
}

I won't dig more on this, you can check all the visibilitySpec options on the following URL: https://github.com/ampproject/amphtml/blob/main/extensions/amp-analytics/amp-analytics.md

Event: ini-load

We can have AMP triggering an event when an AMP Element initial content has been loaded, this is done using a CSS Selector, if the selector is not specified this event will be attached to the current document.

"triggers": {
 "pageLoaded": {
    "on": "ini-load",
    "request": "ga4Event",
    "vars": {
        "ga4_event_name": "page_is_loaded"
     }   
  } 
}

Event: render-start

AMP will trigger this event when an element that embeds other documents in iframes for example the Ads Elements

"adsLoaded": {
    "on": "render-start",
    "request": "ga4Event",
    "vars": {
        "ga4_event_name": "ads_loaded"
    },
    "selector": "#ads"
  }
}

Event: scroll

When a page is scrolled AMP will trigger the scroll event. This trigger provides special vars that indicate the boundaries that triggered a request to be sent. In order to filter which scroll events we want to fire we'll use the scrollSpec object.

We can use the ${verticalScrollBoundary} variable to grab the current scrolling boundary. Here it goes a simple example that will trigger an event when the user scrolls to a 25, 50, 75, 90 of the current page.

"scrollTracking": {
    "on": "scroll",
    "request": "ga4Event",
    "vars": {
        "ga4_event_name": "scroll"
    },
    "extraUrlParams": {
        "event__str_percent_scrolled": "${verticalScrollBoundary}%"
    },
    "scrollSpec": {
        "verticalBoundaries": [25, 50, 75, 90],
        "horizontalBoundaries": [],
        "useInitialPageSize": false
    }
}

Event: timer

As the name suggests this will allow us to send and event based on a regular time interval to GA4. We can also use  timerSpec to control when this will fire.

Please note it's important to know that the timer will keep triggering until maxTimerLength has been reached. Another thing that you need to have in mind is that we can use startSpec to define then this trigger should fire. For example we may want to send a ping event each minute the page is active, but we want to step in if the document is hidden, we could do the following

"triggers": {
  "pingEvents": {
    "on": "timer",
    "request": "ga4Event",
    "vars": {
        "ga4_event_name": "ping"
    },
    "timerSpec": {
      "interval": 60,
      "startSpec": {
        "on": "visible",
        "selector": ":root"
      },
      "stopSpec": {
        "on": "hidden",
        "selector": ":root"
      }
    },
  }
}

Refer to the AMP docs for full details about how to use the timer events.

Event: video-*

I feel this is the most complicated trigger available on AMP. It will allow us to track the video interactions happening on our site in Google Analytics 4.

Multiple video providers are supported by AMP Analytics, these are defined in the following table:

Video ProviderSupport level
<amp-video>Full support
<amp-3q-player>Partial support
<amp-brid-player>Partial support
<amp-brightcove>Full support
<amp-dailymotion>Partial support
<amp-ima-video>Partial support
<amp-nexxtv-player>Partial support
<amp-ooyala-player>Partial support
<amp-youtube>Partial support
<amp-viqeo-player>Full support
Source: https://github.com/ampproject/amphtml/blob/main/extensions/amp-analytics/amp-video-analytics.md

Partial support means that not all the variables may be available:

VarTypeDescription
autoplayBooleanIndicates whether the video began as an autoplay video.
currentTimeNumberSpecifies the current playback time (in seconds) at the time of trigger.
durationNumberSpecifies the total duration of the video (in seconds).
heightNumberSpecifies the height of the video (in px).
idStringSpecifies the ID of the video element.
playedTotalNumberSpecifies the total amount of time the user has watched the video.
stateStringIndicates the state, which can be one “playing_auto”, “playing_manual”, or “paused”.
widthNumberSpecifies the width of video (in px).
playedRangesJsonStringRepresents segments of time the user has watched the video (in JSON format). For example, [[1, 10], [5, 20]]
Source: https://github.com/ampproject/amphtml/blob/main/extensions/amp-analytics/amp-video-analytics.md

For now, we know which video players are supported and which data we will be able to use in our events, know it's time to know which triggers/interactions we'll be able to track, these are:

Trigger NameDescription
video-playVideo stats to play
video-pauseVideo is paused
video-endedVideo Completes (reach end of playback)
video-sessionTriggers when a "video session" has ended. a video session starts when a video is played and finishes when the video is paused, ends, or became invisible
video-seconds-playedThis will trigger each time the defined amount of time is played ( ie: every 10 seconds watched )
video-percentage-playedSame as above we can define which % of the progress we want to trigger this
video-ad-startVideo Ad Starts
video-ad-endVideo Ads Ends

As you can see, the possibilities are almost endless, so we won't be adding examples for all of them, you can find some good examples on AMP documentation. In any case, let's see one example.

Let's say that our AMP page has a YouTube Video embedded:

<amp-youtube
    id="Take off - LGA-YYZ . DL4942 - CRJ-900"
    class="video"
    width="480"
    height="270"    
    data-videoid="Nx-JZ2-kEKU">
</amp-youtube>

#TIP Using the element "id" with the video name will allow us to use it for the video_title parameter for our event

So, for tracking the video plays, we would add the following trigger:

"triggers": {
    "videoPlayEvent": {
        "on": "video-play",
        "request": "ga4Event",
        "vars": {
            "ga4_event_name": "video_played"
        },
        "selector": ".video",
        "videoSpec": {},
        "extraUrlParams": {
            "event__str_video_title": "${id}",
            "event__num_video_duration": "${duration}"
        }
    }
}

Event: visible

Using this event trigger we'll be able to fire an event when the current element(s) defined with our CSS selector are visible within the current browser viewport.

This trigger firing can be fine-tuned using the visibilitySpec , to define the amount of millisecond the element has to be on the screen, or what % of the element needs to be visible to trigger the event.

Refer to the AMP official docs for all the configuration options :)

"triggers": {
    "recomendationsViewed": {
        "on": "visible",
        "request": "ga4Event",
        "vars": {
            "ga4_event_name": "recomendations_block_viewed"
        },
        "selector": "#adobeTargetRecos",
        "visibilitySpec": {
            "waitFor": "ini-load",
            "reportWhen": "documentExit",
            "visiblePercentageMin": 20,
            "totalTimeMin": 500,
            "continuousTimeMin": 200
        }
    }
  }
}

In-Build Auto-Generated Events

There is some data provided by AMP that is available and that can provide some cool events, in this first release I've added two events to track our page loading speed.

WebVitals

You can enable web vitals tracking events, which we expect to be the best ones on AMP ( don't we'? )

To enable this event just set the “ENABLE_WEBVITALS_TRACKING” to true in the main snippet settings, and that will make the tool launch an automatic event (web_vitals) 5 seconds after the page load, with the following available parameters:

ParameterDescriptionExample Value
epn.first_contentful_paintFirst Contentful Paint170.199951171875
epn.first_viewport_readyFirst Viewport Ready164.59999990463257
epn.make_body_visibleMake Body Visible163.40000009536743
epn.largest_contenful_paintLargest Contentful Paint170.299072265625
epn.cumulative_layout_shiftCumulative Layout Shift0.012389976353126643

Performance Timing

This is the same data that we are used to seeing in the Site Speed Reports in Universal Analytics, which includes the time (in ms) to the DomReady event or the time it took to do the DNS Resolution Time.

To enable this event just set the PERFORMANCE_TIMING_TRACKING to true. Then on the page load, a performance_timing event will be fired containing the following parameters.

epn.page_load_timeAmount of time (in seconds) it took that page to load
epn.domain_lookup_timeThe average time (in seconds) spent in DNS lookup for this page
epn.tcp_connect_timeProvides the time it took for HTTP connection to be set up. The duration includes connection handshake time and SOCKS authentication. The value is in milliseconds.
epn.redirect_timeTime taken to complete all the redirects before the request for the current page is made (in ms)
epn.server_response_timeTotal time taken by the server to start sending the response after it starts receiving the request (in ms)
epn.page_download_timeProvides the time taken to load the whole page. The value is calculated from the time unload event handler on previous page ends to the time load event for the current page is fired. If there is no previous page, the duration starts from the time the user agent is ready to fetch the document using an HTTP request (in ms)
epn.content_download_timeProvides the time the page takes to fire the DOMContentLoaded event from the time the previous page is unloaded (in ms)
epn.dom_interactive_timeProvides the time the page to become interactive from the time the previous page is unloaded (in ms)

User Properties

To attach User Properties to our hits, we need to use the extraUrlParams key. We also need to have in mind that a User Property in GA4 can either be a number or a string and while the GTAG/GTM does take care of accordingly casting the values, on AMP we need to define the type.

The way we should define the parameters is this:

"user__str_user_id": "123456",    
"user__num_lifetime_value": "147.34"

As you can see is easy, the first part defines the current scope (which will be event for the Event Parameters in the next section ).

If we add the User Properties to our main snippet, these will be added to all the subsequent events fired within the current page load. In the next example, we are setting 3 User Parameters in our init code snippet that will be persisted across all the events pushed during the current page load.,

{
        "vars": {
            "GA4_MEASUREMENT_ID": "G-THYNGSTER",
            "ENABLE_CONSENT_TRACKING": false,
            "ENABLE_WEBVITALS_TRACKING": true
        },
        "extraUrlParams": {
            "user__str_user_id": "123456",
            "user__str_logged_in": "yes",
            "user__num_lifetime_value": "147.34"
        },
    }

On the other site, we can attach some User Parameters ONLY to the current event, this is done by adding the extraUlrParameters to the current trigger.

        "triggers": {
            "demoClickEvent": {
                "on": "click",
                "request": "ga4Event",
                "selector": "#upgradeMembership",
                "vars": {
                    "ga4_event_name": "member_ship_upgraded",
                },
                "extraUrlParams": {
                    "user__str_last_membership": "premium"
                }
            }
        }

Event Parameters

Same way as the User Properties above, we have two different ways of setting an Event Parameter, to all the current page events or just to the current event, this will be done, again, on the main init snippet, or adding it within the current trigger extraUrlParams.

And of course, remember that they need to define the type, string, or number.

The way we should define the parameter is the same as we did in the User Properties.

"event__str_user_id": "123456",    
"event__num_lifetime_value": "147.34"