Implementation Guide for Events in Google Analytics 4

On the surface, tracking events in Google Analytics 4 (GA4) is fairly simple. Events are, after all, pretty much the only thing you can collect in GA4.

It’s easy to get tied down with endless comparisons to Universal Analytics, though. While I’m steadfastly opposed to the idea that GA4 should resemble Universal Analytics, it’s still important to cleanse the palate and approach GA4’s event tracking with an open mind.

There are some comparisons that can be drawn between the new and the old, but what GA4 might lack in some features and use cases, it more than makes up for this with a more flexible data structure.

In this implementation guide, I’ll take a look at the structure, composition, implementation, and evaluation of Google Analytics 4 events.

For more details on how events work in Google Analytics 4, do check out the following excellent resources:

These resources outmatch anything I could create by a wide margin, so make sure you bookmark them and follow them religiously!

This guide will be focused on data collection for the web, mainly using Google Tag Manager. While mobile app events and gtag.js implementations will be discussed in passing, I have chosen this particular focus due to the overwhelming popularity of Google Tag Manager as the primary means for collecting data to Google Analytics.

Structure of an event

An event hit, when sent to GA4, comprises an event name and parameters.

Furthermore, parameters can be divided into automatically collected parameters and custom parameters.

For example, here is an event named page_view, sent with a bunch of parameters to GA4:

There are a handful of special (reserved) parameters collected for measurement, such as:

  • tid - Measurement ID
  • sr - Screen resolution
  • _dbg - Debug mode active
  • seg - User is considered engaged for this session

There are more details about these automatically collected parameters in a later chapter.

I’m also sending a couple of custom parameters:

  • epn.readibility_median_grade - A custom parameter sent as a number.

  • epn.readability_reading_time - A custom parameter sent as a number.

The prefix epn means a number parameter, whereas the prefix ep means a text parameter.

Here’s an example of an event that sends text parameters and user properties (prefixed with up):

Note that GA4 batches events, so if you fire more than one event in a very short time window, the events will be sent together in a single hit:

The hit above sent two events: a page_view event and a scroll event, both with some custom parameters.

In this article, I’ll help you come to terms with the terminology used above.

Event tag in Google Tag Manager

While Google Analytics 4 implementation via Google Tag Manager is still somewhat in beta (even if GA4 itself isn’t), it’s still more than possible to set an entire GA4 implementation up with GTM.

GTM has two tag types for collecting data to Google Analytics 4.

  • Google Analytics: GA4 Configuration - Recommended for GA4 data collection. Establishes the base settings and is used as the underlying configuration for event tags.

  • Google Analytics: GA4 Event - Tag type used for sending events to GA4.

In order to send your custom event hits to GA4, you need to establish a configuration first. You can check out my original guide for some (slightly outdated) tips on how to do this. See also the chapter below for more details on how the configuration tag and event tags interact.

Within the GA4 Event tag type, you have some fields and settings you can configure the tag with.

Setting Description
Configuration Tag If you want to use a configuration tag, you can select it here. Alternatively, you can ignore the configuration tag and just set a measurement ID manually.
Event Name The name of the event as will be shown in GA4 reports. The recommended format is snake_case. Try to avoid using the same names as automatically collected events unless you know what you’re doing.
Event Parameters Add any parameters you want to send with the event to GA4. If you use a configuration tag, then any parameters set in that will also be applied to this hit (with the values fixed to what they were when the config tag was fired). Remember that if you want to use parameters in reports, you need to register them as custom dimensions.
User Properties Add any user properties you want to set with this event. User properties are similar to user-scoped custom dimensions in Universal Analytics: they apply to all future hits from this user, until a new value is set for each. You do need to register them in GA4 for them to be available in reports.

Once you’ve configured the event name and the parameters to your liking, all you need to do is add a trigger to the tag and start collecting the data.

If you’re using Google Tag Manager’s Preview mode to test your tag with (as you should), the hit will automatically have the _dbg parameter set, and you’ll see it in DebugView.

Quotas and limits

Unfortunately, GA4 events have some arbitrary, and frankly very annoying, quotas and limits that you need to be aware of. See this article for the full list.

For example, you should avoid collecting more than 500 unique event names in GA4. Even though this limit doesn’t seem to be technically enforced (you can collect 500+ unique events), you’re still in violation of a collection limit, and Google can start enforcing this limit at any time.

Similarly, while you can collect more than 25 parameters in a single event tag, only 25 would be passed on to reporting.

Note that even if you go over the quotas in event parameters and custom dimensions, they will still be passed through to the BigQuery export, which makes the BigQuery export a super valuable thing to set up in every single GA4 implementation!

These quotas and limits will most likely change as the platform matures, so I recommend keeping the documentation bookmarked so that you aren’t blindsided by changes that could impact your data quality.

Enhanced measurement

Enhanced measurement is really neat. It takes a leaf out of autotrack.js’ book, and with a few flavors from Google Tag Manager’s triggers, and bakes it all into a simple, automated tracking tool orchestrated by gtag.js itself.

Confused? Sorry about that. In short, enhanced measurement tracks stuff automatically just by adding the configuration tag to the page.

Enhanced measurement is controlled from the GA4 admin settings.

If you enable enhanced measurement, the following events will be tracked automatically (assuming you don’t disable them) - see the official documentation for more details.

  • page_view - sent with the page load as well as browser history events with single-page apps.
  • scroll - sent when the user scrolls to the bottom of the page.
  • click - sent when the user clicks an outbound link.
  • view_search_results - sent when the user loads a page with a configured search query parameter.
  • video_start, video_progress, video_complete - sent automatically when the user starts to view a video, progresses past predefined percentages of the total video length, and reaches the end of the video.
  • file_download - sent when the user clicks a link to a file with one of the predefined extensions.

Note that as these are, for all intents and purposes, automatically collected events, you can’t change or modify the parameters sent with these hits. You can add parameters to them by setting them globally in a configuration tag, but check out this later chapter for some serious caveats to setting event parameters in a configuration tag!

You can always opt-out of enhanced measurement tracking by modifying the GA4 data stream settings. For example, I’m not a huge fan of the scroll event, so I never enable it - I’d rather have full control over something as potentially spammy as scroll tracking.

BigQuery export and GA4 events

If you’ve been reading my blog, you might have noticed that I’m a huge fan of Google BigQuery.

One of the main reasons to stop reading this right now and go and implement GA4 is because it comes with a free BigQuery export of all your GA4 data!

BigQuery itself has, naturally, costs associated with usage, but you don’t have to pay Google Analytics for the privilege of accessing raw data in the export.

For the purposes of event tracking, the BigQuery export has a huge, huge benefit.

Quotas that would apply to events in reports do not apply to the data exported to BigQuery!

In other words, if you’ve reached the maximum number of custom dimensions, for example, all the custom parameters that you send to GA4 will still be exported into BigQuery.

This means that you might end up with two different approaches to the data collected by a single platform.

First is the data that’s surfaced in the reporting UI. This is your everyday data - subject to quotas and limitations, but easy to manipulate and explore for useful insights.

The second is the raw data in BigQuery. This is the data set that you can take, sculpt, engineer, extract, and manipulate however you wish. It lets you break free of the prescriptive schema imposed by GA4, and you can start to truly unravel the wonders of the event stream model.

If you’re curious about how BigQuery and GA4 play ball together, takes a look at this article that Pawel Kapuscinski and I wrote!

Event types

Considering that GA4 is basically an event stream model, it’s not surprising that events themselves are categorized in a number of different ways.

There are the automatically collected events, which GA4, one way or the other, collects without needing manual tagging.

Then there are a bunch of recommended events, which Google strongly recommends you to use (if they are applicable for your measurement needs).

Finally, if there are no automatically collected events or recommended events that suit your use case, you can always use custom events.

Automatically collected events

Automatically collected events are enumerated in this document. Both app (Firebase SDK) and web (Global Site Tag / Google Tag Manager) automatically collect some events, and if you’ve enabled enhanced measurement for the web stream, even more events are collected without the need to manually tag the site.

Just to reiterate - an event is an automatically collected event when it doesn’t require adding a manual tag or code snippet for the data collection. Some automatically collected events collect additional parameters that help flesh out the event metadata.

Here are the automatically collected events for the web:

Event name Description Trigger
click Outbound link collection (when enabled via enhanced measurement). Click on a link that leads out of the current domain.
file_download File download tracking (when enabled via enhanced measurement). Click on a link that downloads a file with one of the predefined extensions.
first_visit First visit from a given client / user ID. When an event is collected from a new client or user ID.
page_view When a page is loaded or a browser history event happens (when enabled via enhanced measurement). Page load or history event (pushState, replaceState, popstate).
scroll Scroll tracking (when enabled via enhanced measurement). Scroll to the bottom of the page.
session_start An event starts a new session. 30 minutes have expired since the last event sent by the current user.
user_engagement The session is considered an engaged session. Active engagement with the website for more than 10 seconds, or a conversion event collected, or two or more pageviews collected.
video_complete YouTube video was watched to completion (when enabled via enhanced measurement). Video ends.
video_progress YouTube video is watched past specific progress milestones (when enabled via enhanced measurement). Video progresses past 10%, 25%, 50%, and 75%.
video_start YouTube video watching begins (when enabled via enhanced measurement). Video starts.
view_search_results A search results page is viewed (when enabled via enhanced measurement). A page is loaded with search query parameters in the URL.

As you can see, many of the events listed have to do with enhanced measurement. However, there are some key events that are always collected, and interestingly they might not actually have their own hit types at all. Instead, they are derived from other events that are collected on the site.

For example, let’s say you collect a page_view event from a user who hasn’t visited your site before, and this page_view is the first hit they send. This is what you’ll see in the request:

The _fv parameter denotes that this is a first visit event (the user is a new user), and the _ss parameter means that this event started a new session. When you look at the data in GA4, this one single page_view event actually splits into three separate GA4 events!

If you look at the request again, you’ll see that the seg parameter has the value 0. This means that the session is not considered engaged. When the user then sends another page view before the session timeout (30 minutes), this is what the parameter value becomes in the new page view event:

On the web, user_engagement is collected in milliseconds, with each event that classifies as an engagement event sending the current engaged time with the _et parameter. This parameter value isn’t surfaced in GA4 events, but it is available in BigQuery:

For a platform that celebrates not being bound to an arbitrary concept of a session, it sure does seem like it hasn’t shrugged off this arbitrariness entirely. Time will tell how these events will properly translate to the reports.

Having said that, the idea that a session is just an annotation (the _ss parameter and the dedicated events) rather than a key aggregation (as in Universal Analytics) is inspiring. It’s much easier to reconstruct your own concept of what a meaningful aggregation metric is.

See this article by Jules Stuifbergen for an example of how you can build your own session approach using BigQuery and the raw data export from GA4.

Recommended events are a curiosity, mainly because at the time of writing they have very little functional use in GA4.

Google has a bunch of support pages focused on telling you what semantics to use when collecting events for specific segments and industries.

For example, when the user logs in, Google instructs you to use an event with the name login and a parameter with the name method.

Why? Who knows. There’s no benefit in the current events to collecting an event with the name login as opposed to, for example, user_login. However, it’s a recommended event, so it has to carry some weight, right?

Well, again, time will tell. I’m assuming some of these events get collected into their own dedicated reports. Hopefully, some will even be exempt from current quotas, ecommerce events in particular.

In fact, ecommerce events are an exception to the rant above - they do have functional weight as they populate the monetization reports. Take a look at my guide for ecommerce implementation in GA4 for more information.

For now, if you want to add a new data collection tag to the site, take a look at the list of recommended events and see if you find one that fits your use case. If you do, use that, and try to use the recommended parameters as well. There’s no harm in doing so, and at best you’ll enable some cool functionality that Google will release in the near future.

However, if you don’t find a recommended event that fits your use case perfectly, don’t waste time trying to shoehorn a square piece into a round hole. Use custom events instead.

Custom events

Once you’ve exhausted the list of automatically collected and recommended events, and you can’t find one that fits your tracking need, you can always send whatever event name you wish. Just note that there are some reserved names.

Custom events are super powerful, even if Google is oddly diminutive about their use.

For some reason, Google really wants you to use anything but custom events. I don’t really agree with this. Custom events are your chance to choose how GA4 will serve your organization. Even if the data isn’t surfaced in most of the standard reports (odd argument because custom events are certainly available in all the key parts of the reporting UI), they will be available in BigQuery.

No matter how long they’ve been in the game, I don’t think Google can or should prescribe how you are to do event tracking on any given website. Thus, take their recommended events with a grain of salt, especially since they have little functional impact today, and use custom events at your leisure.

Familiarize yourself with BigQuery - it really is the key to making the most of GA4. I love the analysis hub, and I’m sure I’ll use the data API religiously once it has a stable release, but nothing - nothing - will replace BigQuery as my analysis tool of choice.

Event parameters

As mentioned above, events comprise an event name and parameters.

Parameters are further split into special parameters (reserved names that contain technical details about the hit), custom parameters (text and number parameters), and user properties.

Special parameters

These parameters are collected with every single event, regardless of if it’s collected automatically or tagged manually:

  • ul or language, which collects the browser language (e.g. en-us).
  • dl or page_location, which collects the current URL.
  • dr or page_referrer, which collects the referrer URL (or empty string if not available).
  • dt or page_title, which collects the page title.
  • sr or screen_resolution, which collects the screen resolution.

There are additional “special” parameters that are collected in some cases. These include (but are not limited to):

  • cid - Client ID; the cookie identifier that helps GA4 recognize repeat visits from the same device.
  • uid - User ID; an identifier you can set manually based on an authentication token, for example. This helps you unite cross-platform and cross-device browsing under a single login identifier.
  • sid - Session ID.
  • sct - Session count; how many sessions have been collected from the current user. NOTE! As this is collected with a client-side cookie that is not related to the Client ID cookie, it’s possible this number doesn’t actually reflect reality, and a BigQuery analysis might return different results, for example.
  • seg - Session engaged; if the current session is considered “engaged”.
  • _fv - First visit; if the current hit is the first hit collected from this user.
  • _ss - Session start; if the current hit started a new session.

Custom parameters

You can set custom parameters by adding them manually to your tags. Automatically collected events can collect some custom parameters automatically as well.

Custom parameters come in two flavors:

  • Text parameters, when the value set to this parameter is not a number (ep. prefix in the hit).
  • Number parameters, when the value set to this parameter is a number (epn. prefix in the hit).

The main difference is that text parameters can be used as custom dimensions and number parameters can be used as custom metrics.

To set parameters, you add them into the event tag like this:

Here I’ve configured the two variables ({{Readability - Get median grade}} and {{Readability - Get reading time}}) to return numbers, so they get automatically converted to number parameters. The user_level is cast into a string by Google Tag Manager (as it’s hard-coded into the tag field), and thus it’s treated as a text parameter.

The only caveat to setting event parameters in the tag is that event parameter names cannot begin with google_, ga_, or firebase_.

When you want to use event parameters for analysis, they need to be registered as custom dimensions and metrics first.

If you have the BigQuery export enabled, all event parameters will be exported to BQ whether they’re registered as custom dimensions / metrics or not.

User properties

User properties can be set with events as well. A user property acts similarly to Universal Analytics’ user-scoped custom dimensions, with the main difference that they apply from the hit they were set onwards rather than the session* they were set as in Universal Analytics.

User properties need to be registered in the GA4 user interface for them to be available in reports.

To set a user property, simply add it to the tag in the correct place:

You’ll see that it’s included in the hit with the up. prefix.

There are some user property names that cannot be used as they are reserved. Check the list here.

Configuration tag and events

When you create an event tag in Google Tag Manager, you have the option of choosing a configuration tag to set things up for the event hit.

The configuration tag is, surprise surprise, the same as the config command with the global site tag (gtag.js).

You can use it to establish a shared configuration for your event hits, and you can even use it to set persistent event parameters.

The most common use case for the configuration tag is for configuring the GA4 implementation. You can add fields that configure how cookies are set or to which endpoint to send the hits (useful if you want to collect the data with a server container).

However, you can also set persistent events and user properties with the configuration tag as well. Any field names that you set that are not reserved (e.g. cookie_domain) will be treated as event parameters, and they will be included with every single event that uses this configuration tag.

Note that there’s one potentially pretty devastating caveat with setting persistent event parameters and user properties with the configuration tag:

The values will be fixed to their initial values and will NOT be updated with every event.

In other words, when you set an event parameter or property in the configuration tag, it will be sent with every single event that uses the configuration tag. However, the value will always be whatever it was when the configuration tag fired.

It’s thus not a useful way to set dynamic values like event timestamps or similar. Instead, it should be used to set parameters that are unlikely to change from one event to the next.

Ecommerce events

Ecommerce events are, at the time of writing, part of a very special and unique cast: they are recommended events that actually do something.

The typical structure of an ecommerce event is a prescribed event name (e.g. add_to_cart) together with an items array that reflects the items or products that were involved in the action.

If you want to start tagging your site for ecommerce data collection, I’d like to direct you to my comprehensive implementation guide for GA4 ecommerce.

One curiosity about ecommerce events is that the items structure is an actual array. Typically, event parameters are primitive values such as strings or numbers.

The items array is a refreshing change and hopefully, hopefully, a sign that GA4 will start opening up the rigid data structure inherited from Universal Analytics, where the only multi-dimensional values you can collect are ecommerce objects.

I mean, that syntax of turning the array of objects into this weird, proprietary string format doesn’t make very much sense either, but at least you can send something other than just primitive values to GA4.

I really hope they’ll extend the data model to accept JSON values, for example. Being able to send an array or object as an event parameter and then having it be automatically parsed for the reporting UI (and nested for BigQuery) would be an absolutely incredible feature to have in GA4. Something that’s been missing from Google’s analytics tools since day one.

Debugging events

For debugging the event implementation, there are three tools I use.

  1. The new Tag Assistant preview mode for making sure that the event fields are populated correctly.

  2. The web browser’s developer tools for making sure the hit payload contains all the correct values.

  3. GA4’s DebugView for making sure the data ends up in GA4 in the correct format.

If you’re a fan of browser extensions, I recommend David Vallejo’s excellent GTM/GA Debug which comes equipped with full GA4 debugging support!

Tag Assistant Preview

When you’re debugging your GA4 setup with Tag Assistant, you have two ways of validating your setup.

First, you can debug the GTM container where you’ve implemented the GA4 tags. Choose the trigger event (e.g. DOM Ready), and make sure all the fields in the event tag look right.

You can also select the GA4 measurement ID to see if the hit actually dispatched.

Note how the page_view event name is translated to Page View in the Tag Assistant view. Unfortunately, the debug experience for a GA4 tracker is still a bit unwieldy, and you just have to know what event names to look for. Luckily it’s usually easy to click through the handful of available messages to find the one where your hit was sent.

When debugging with Tag Assistant, your focus should be on verifying that all the fields and parameters resolve to their correct values. If something is off, you know it needs to be fixed in GTM.

Developer tools

Even though the new GA4 measurement ID debug view shows you the hit was dispatched with certain values, I still like to do network request debugging using the browser’s developer tools.

In GA4, the request is sent to a /collect endpoint just as with Universal Analytics. However, you’ll be able to identify a GA4 payload by the v=2 parameter as well as the GA4 measurement ID sent with the tid parameter.

Check the requests, make sure they return a 204 status code.

GA4 requests return a 204 status because the content type for GA4 requests is text, but the response is always empty. It’s still a successful request!

Then go through the parameters and make sure all the values look good. This is what the actual request to GA4 will be, so if something is off at this point you’ll know you need to fix it at its source (e.g. GTM).


Finally, if you’re previewing a Google Tag Manager container, the hit will be automatically sent to GA4’s DebugView. If you want to manually enable DebugView for other hits (e.g. when not previewing a container), you can always set the debug_mode field to any value (though true probably makes most semantic sense).

When the request includes the debug mode parameter (_dbg), you’ll find a real-time data stream of similar hits in the DebugView report in GA4.

I can’t emphasize enough how stupendously amazing this feature is. To be honest, it’s my favorite part of GA4’s reporting interface!

A real-time stream of debug events, with all the parameters available for perusal, and you can even see how some automatically collected events are generated on the fly!

So, as the last step of debugging your event implementation, make sure DebugView agrees with what you think the event should look like. If everything looks good, the next step is to locate the data in your standard reports (might take some time to reach those).

If something looks off in DebugView, the issue might be at the source (something was wrong with GTM after all), or you could have messed something with GA4’s data stream settings.

Debugging is an acquired skill - it’s not easy to do because you need to know what you’re looking for before you can flag it as a potential issue. However, what matters is consistency and the discipline to approach debugging with an end-to-end process rather than just cherry-picking individual parts of the event implementation for analysis.

It doesn’t make sense to only look at your GTM setup if the issue can also be in your GA4 settings or vice versa.


I hope this guide has been useful! Setting up event tracking with GA4 can be difficult at first because there’s a lot of baggage inherited from Universal Analytics.

I want to share something that I’ve mentioned numerous times in trainings and discussions when GA4 comes up:

I don’t want GA4 to repeat Universal Analytics’ mistakes!

So many people are holding off with trying GA4 out because they want it to have everything that Universal Analytics had first. What’s the point? Universal Analytics’ data model was far from perfect. Sessions, sampling, limited custom dimensions, views, filters, client IDs, just to name a few.

I really hope that GA4 finds a way out of the shackles imposed upon it by virtue of being an analytics platform created by the same company that also created Google Analytics.

GA4’s data model is strictly event-driven, even if there are whiffs of sessions and users here and there. The hit-stream vibes are strong with this one, which might make it difficult to adjust after the awkward hierarchical event model of Universal Analytics.

With this guide, my purpose has been to show how you can approach implementing event tracking with Google Analytics 4. Remember that GA4 is still in its infancy - there’s likely to be lots and lots of feature releases between now and soon. You’ll be disappointed by some and elated by others, but one thing’s for certain: it’s a brave new world for Google’s analytics tools. If they don’t come with guns blazing out of the gate, they risk losing foothold in a world where viable analytics platforms are a dime a dozen.

I’m excited at the prospects of all the things you can do with GA4’s event stream model, but I’m also a bit worried about how strong the pull of Universal Analytics will be.

What do you think about GA4’s event tracking capabilities? Or, if you wish, feel free to unload your thoughts on GA4 in general.