Skip to content
ceaksan
gtm

Global Site Tag (gtag.js) Event Tracking

Manage multiple GA/GA4 properties with gtag.js. Learn config, set, event, get, consent commands and the send_to/groups routing model.

Dec 4, 2018 1 min read Updated: May 19, 2026
TL;DR

gtag.js (now Google tag) sends data from a single snippet to multiple Google products. Use config to set up targets, send_to to route events to specific properties, groups to bundle targets. GA4 dropped the fixed Category/Action/Label model in favor of an event name + free-form parameters (method, value, currency, items[] and so on). Universal Analytics was shut down in July 2023.


gtag.js (officially renamed to Google tag) is the unified tagging layer that lets a single snippet send data to multiple Google products (Analytics, Ads, Campaign Manager). The API is one function: gtag(<command>, <params>). There are five commands1: config (target setup), set (global parameters for the page), event (event delivery), get (read values via a callback), and consent (Consent Mode). Parameter scope follows event > config > set precedence, and multi-property management, grouping (via the groups parameter), and event routing (via the send_to parameter) all sit on top of this structure.

note

The analytics.js, ecommerce.js, ga.js, and ec.js libraries that were still in use when this post was first published are now retired. Today the measurement layer runs almost entirely on Google tag (gtag.js) and Google Tag Manager (GTM), and most vendors have migrated as well. That said, packaged e-commerce platforms and local apps tend to lag behind, so understanding what happens at the command level still pays off.

In this post I’ll walk through how gtag.js commands shape event delivery, what changed between GA and GA4 parameters, which settings have moved from code to the UI, and the common data flow problems you’ll run into (especially “Config command out of order” type ordering bugs).

Global Site Tag (gtag.js) Event Tracking

Both Google Ads and Google Analytics can be initialized from a single global snippet via the config parameter.

info

Global Site Tag (gtag.js) is now officially called the Google tag2. Every existing gtag.js install was migrated automatically; you don’t have to change anything in your site code. Configuration can now also be managed from the Google Ads, Google Analytics, and Google Tag Manager UIs. The gtag('config', ...) and gtag('event', ...) commands in this post continue to work the same way.

Event handling in Global Site Tag (gtag.js) follows the same skeleton. That’s what makes it easy to declare multiple sites (not necessarily subdomains) and multiple Google Analytics and Google Ads properties from a single snippet with separate cookie namespaces.

gtag("config", "GA_TRACKING_ID", {
  send_page_view: false,
});
gtag("config", "GA_TRACKING_ID_2");
gtag("config", "GA_TRACKING_ID_3");

The block above disables page_view tracking for the property GA_TRACKING_ID while GA_TRACKING_ID_2 and GA_TRACKING_ID_3 keep collecting page views as usual. The same shape also unlocks grouping3. Moving from a Universal Analytics property to a Google Analytics 4 (GA4) property is also just a matter of adding the GA4 GA_TRACKING_ID to a config call.

By default, properties declared this way land in the implicit default group. To declare it explicitly:

gtag("config", "GA_TRACKING_ID_1", {
  groups: "default",
});
gtag("config", "GA_TRACKING_ID_2", {
  groups: "default",
});

And to assign a user-defined group:

gtag("config", "GA_TRACKING_ID_1", {
  groups: "access",
});
gtag("config", "GA_TRACKING_ID_2", {
  groups: "access",
});

These property IDs now belong to the access group. Grouping is what lets you route event data to a specific subset of properties.

Sending Events

A gtag.js event is just an event_name plus a parameter list. Universal Analytics reports used to present events as Category, Action, Label, and Value; GA4 dropped that fixed shape in favor of an event name + free-form parameters. In both models, some events only fire after a specific user action (a view, a click, a submit), so the event name is what ties the payload to the trigger. That’s why I recommend treating event naming as a deliberate exercise with a clear pattern.

gtag("event", "event_name", {
  send_to: "access",
  parameter_1: "value_1",
  parameter_2: "value_2",
});

GA4 now ships with automatically collected events4:

  • language
  • page_location
  • page_referrer
  • page_title
  • screen_resolution

That means common user actions flow directly into the Events report without extra code. GA4’s event model itself works differently from Universal Analytics, though, and I’ll get into that in a moment.

The event above routes to the access group. If send_to doesn’t name a custom group, events go to the default group. To route a single event to multiple groups:

gtag("event", "event_name", {
  send_to: ["group_name_1", "group_name_2"],
  parameter_1: "value_1",
  parameter_2: "value_2",
});
warning

As of July 2023, Universal Analytics (UA) has been replaced by Google Analytics 4 (GA4) at the property level. UA properties no longer process new data.

With Universal Analytics gone, there’s no reason to maintain two parallel models anymore. GA4’s event-name + parameter shape is the only path going forward, and trying to carry the old Category/Action/Label habit into GA4 creates inconsistency rather than coverage.

// GA — sending page view separately
gtag("config", "GA_MEASUREMENT_ID", {
  page_title: "homepage",
  page_path: "/home",
});

// GA — disabling automatic page view
gtag("config", "GA_MEASUREMENT_ID", {
  send_page_view: false,
});

// GA — sending page view as an event
gtag("event", "page_view", {
  page_title: "<Page Title>",
  page_location: "<Page Location>",
  page_path: "<Page Path>",
  send_to: "<GA_MEASUREMENT_ID>",
});

Universal Analytics event anatomy (historical reference):

NameParameterStatus
Categoryevent_categoryRequired
Actionevent_actionRequired
Labelevent_labelRecommended
ValuevalueOptional

GA4 has no fixed anatomy. An event is a name + a free-form parameter list. Recommended events have predefined parameters; in custom events you’re free to attach whatever parameters you need5.

Common GA4 recommended event parameters:

EventRecommended parameters
loginmethod
sign_upmethod
searchsearch_term (required)
view_itemcurrency, value, items[]
add_to_cartcurrency, value, items[]
purchasetransaction_id (required), value, currency, items[]
generate_leadvalue, currency

For example, in GA4 login is a recommended event and the method parameter carries the channel the user signed in through:

gtag("event", "login");
gtag("event", "login", {
  method: "Google",
});

Custom event shape, when you want to use your own event name and parameters instead of the recommended ones5:

gtag("event", "newsletter_signup", {
  signup_location: "footer",
  newsletter_type: "weekly",
});

To send the login event to multiple groups:

gtag("event", "login", {
  send_to: ["default", "group_1", "group_2"],
  method: "Google",
});

Grouping, the global snippet, property-scoped event handling, and recommended events are just a handful of what gtag.js offers.

send_to and groups Behavior

When you don’t pass send_to, the event lands in the implicit default group filled by every config call on the page. When you do pass it, the value can be a single property ID, a group name, or a mixed array:

gtag("event", "add_to_cart", {
  send_to: ["G-XXXXXX-1", "AW-YYYYYY", "agency"],
  ecommerce: {
    //...
  },
});

Two rules in the official docs1 are easy to miss and both cause silent data loss when ignored:

  • Group names cannot contain a dash (-). agency-team won’t work; use agency_team or agencyTeam.
  • config already adds the target to the default group. Writing groups: 'default' is redundant, though declaring the group name explicitly isn’t wrong either.

Settings That Moved from Code to the UI

A lot of the settings that used to live inside gtag('config', ...) are now configurable from the Google tag UI itself (Google Ads > Data Manager, GA4 > Data Streams, GTM > Google tags) without touching site code6. That’s a big lever for anyone working with a CMS or a deploy pipeline that’s slow to change.

Settings you can now flip from the UI:

  • Automatic event detection: page_view, scroll, outbound click, form interaction, video engagement, file download. All on by default; turn off the ones you don’t want with a single toggle.
  • Domain configuration: cross-domain measurement (what linker used to handle).
  • User-provided data: automatic detection, CSS selectors, or manual code for enhanced conversions.
  • Internal traffic: IP-based traffic_type tagging (used with GA filters).
  • Unwanted referrals: exclude specific domains from being counted as referrals (ignore_referrer).
  • Session timeout: default 30 minutes, engaged session threshold 10 seconds.
  • Cookie settings: expiration and update behavior.
  • Consent mode override: per-region default consent state.

The practical effect: you can leave a single tag snippet in your code and toggle send_page_view, cross-domain measurement, automatic scroll/click tracking, and so on from the UI. The gtag() calls in your code are still useful for per-property tuning, event delivery, and custom parameters.

Event Callback for Synchronous Flows

When the trigger is a form submit or a redirect bound directly to onclick, the browser can cancel the event request mid-flight; the page changes before the measurement completes. event_callback and event_timeout exist for exactly this case7:

function trackedSubmit(form) {
  gtag("event", "generate_lead", {
    send_to: "AW-YYYYYY",
    value: 99.99,
    currency: "USD",
    event_callback: function () {
      form.submit();
      console.log("Event completed");
    },
    event_timeout: 2000,
  });
  return false;
}

event_callback fires when the event has been processed; event_timeout (in milliseconds) is the ceiling that fires the callback even if the request never completes. Used together, the measurement is safe and the user flow stays unblocked.

Property and Event Flow Separation

Even when the code looks right, you can still see data flow gaps. A few common reasons:

Config Command Ordering

One of the more frequent warnings in Google Tag Diagnostics is “Config command out of order”8. If an event command runs before the matching config command for that property on the page, the event payload behaves unpredictably; in particular, settings like client_id, cookie_domain, cookie_prefix, linker, server_container_url, session_id, transport_url, and user_id don’t take effect.

Wrong order:

<script>
  gtag("config", "G-ABCDEF");
</script>
<script>
  gtag("event", "generate_lead", {
    send_to: "G-1234567",
    currency: "USD",
    value: 99.99,
  });
  // If config G-1234567 lands here, the event already fired without it.
  gtag("config", "G-1234567", { user_id: "U1234" });
</script>

Right order: all config commands load before any matching event command.

<script>
  gtag("config", "G-ABCDEF");
  gtag("config", "G-1234567", { user_id: "U1234" });
</script>
<script>
  gtag("event", "generate_lead", {
    send_to: "G-1234567",
    currency: "USD",
    value: 99.99,
  });
</script>

This usually surfaces in multi-property setups where CMS templates inject snippets in the wrong order. Even when send_to names the target explicitly, the data is still incomplete if the target’s config runs later than the event.

The official guidance goes one step further: call config only once per Google tag, and use set for any later calls1. Running a second config for the same property (for example to add user_id later) is exactly what causes the unpredictable behavior; gtag('set', { user_id: 'U1234' }) is the safer pattern.

Tag Merging and Destination Abstraction

When you merge two tags in the Google tag UI (say an AW-XXXXXX with a G-YYYYYY) via “Combine Google Tags”, one of them becomes the carrier (the AW- or a GT-) and the other is demoted to a “destination” inside it; it’s no longer an independent tag. The practical consequence: there’s no standalone gtag.js file on the CDN for the demoted G-YYYYYY anymore.

If you put this line in your source:

<script
  async
  src="https://www.googletagmanager.com/gtag/js?id=G-YYYYYY"
></script>

The browser console returns net::ERR_ABORTED 404 (Not Found). The carrier (AW-XXXXXX) loads fine, but pulling a standalone G- library on the same page fails because that G- ID is now a destination under the parent tag, not a tag of its own.

The fix isn’t to add a second gtag.js to the page; it’s to route the event via send_to over the carrier library that’s already loaded:

gtag("event", "purchase", {
  send_to: "G-YYYYYY",
  transaction_id: "T_12345",
  value: 299.99,
  currency: "USD",
});

The merge looks reversible in the UI, but in some cases there’s no going back once you’ve picked the carrier. Be deliberate about which tag becomes the “blueprint” before you merge.

warning

A gtag.js 404 isn’t only a missing measurement problem. With the library unavailable, gtag('consent', 'update', ...) calls have nothing to listen to them; the visitor stays in the default denied state even after accepting the banner, and the consent signal never reaches the ad platforms. Since Consent Mode is now the primary control point for Google Ads data flow, guaranteeing the library loads is also a compliance concern.

GTM Auto-Loading the Google Tag

As of 10 April 2025, GTM containers that hold Google Ads or Floodlight tags automatically load the matching Google tag when an event fires. The intent is helpful: even if you forgot to configure the tag, events still reach Google. The catch is that this auto-loaded library runs detached from your manual configuration and comes with a few gaps:

  • page_view doesn’t fire automatically.
  • Enhanced measurement events (outbound click, scroll, file download) aren’t triggered by this auto-loaded library.
  • If a manual AW- or G- snippet is already on the page, two simultaneous Google tag loads can cause double counting or net::ERR_ABORTED errors.

Practical takeaway: if you’re using GTM, set up the Google tag inside GTM explicitly and don’t leave a parallel manual snippet on the page. If you really need both, watch the Tag Diagnostics “Tag coverage” report regularly.

Automatic Property Migration

When Universal Analytics was being replaced by Google Analytics 4, you probably saw the notification banner in the UI. If the migration wasn’t done by the deadline, it happened automatically as scheduled. That automatic step left some properties in odd configurations. G- and GT- are the most common measurement ID prefixes for the GA4 config call, but in some cases I noticed MC- and AW- ending up in the list too, depending on which other properties the UA property was linked to.

That creates a split between config and event targeting. config essentially carries the tracking library and the configuration, while event delivery may need a different property declared via send_to. This overlaps with the “Tag Merging and Destination Abstraction” and “Misconfiguration” sections.

Misconfiguration

You can create a GA4 property through several different apps and integrations. Merchant Center and Google Ads are the most common ones. In these linked setups, sometimes the GA4 configuration is set up as a target receiving data from another property. In other words, the actual data source is Google Ads while GA4 is added as a destination and data flows in through Google Ads. In cases like that, the config call points at Google Ads, but the data (external event data and so on) needs an explicit send_to because the event data delivered to Google Ads can differ from what GA4 actually expects.

If your GA4 event reports are full of page views and other automatically collected events but missing ecommerce and other custom events, the likely cause is the inter-property data transfer mismatch described here.

Sandbox Limitations

If you’re using Shopify Pixel, you can hit several of these problems at once. First, you might have had issues during the Universal Analytics to GA4 transition; see Automatic Property Migration. Second, if you created Google Ads through the Google & YouTube app and let it set up properties automatically, the properties may have been linked together as targets; see Misconfiguration. Finally, pixels run inside a sandbox by design. The first data points arrive during load via page_viewed (at the most basic level). If the setup script doesn’t load through your page flow, or if config lags behind (because of various checks), the event payload has no idea where to go. In situations like that, using send_to explicitly on every event prevents the flow problems before they happen.


Footnotes

  1. Google tag API reference. Google Developers 2 3
  2. About the Google tag. Google Tag Manager Help
  3. Send data to Google Analytics with gtag.js. Google Analytics
  4. Automatically collected events
  5. Measure Google Analytics Events. Google Analytics 2
  6. Configure your Google tag settings. Google Tag Manager Help
  7. Google tag parameter reference. Google Developers
  8. Troubleshoot: Config command out of order. Google Analytics Help
Key Takeaways
  • 01 config sets up targets, set holds global parameters, event sends data, send_to routes to specific targets
  • 02 GA4 has no category/action/label model; events use a name + recommended or custom parameters
  • 03 Recommended event examples: login → method, purchase → transaction_id + value + currency + items[]
  • 04 Use send_to explicitly in multi-property setups to keep data flow predictable
  • 05 Load all config calls before any event calls to avoid 'Config command out of order'
Frequently Asked Questions (FAQ)
+ How do I manage multiple GA properties with gtag.js?

Make a separate config call for each property. Use the groups parameter to bundle properties together. Use send_to to route events to specific groups or property IDs.

+ What's the difference between GA and GA4 event parameters?

Universal Analytics expected every event to carry event_category, event_action, event_label, and value. GA4 dropped that fixed shape; an event is just a name + a free-form parameter list. Recommended events (login, purchase, search and similar) have predefined parameters, and custom events can carry any parameters you define.

+ What does the send_to parameter do?

It routes event data to specific property IDs or groups. If you don't specify it, the event goes to the default group, which holds every target you've configured on the page.

+ Does the automatic GA4 migration cause data loss?

Mismatches between config and event targets can appear after automatic property migration. Using send_to explicitly on each event prevents the silent loss.

+ What should I watch for when using gtag.js with Shopify Pixel?

Sandbox limitations, automatic property migration, and configuration drift can all bite at once. Specify send_to explicitly on every event.