I want to track custom events in GA4 via GTM. I have setup a GA4 custom event tags which works fine. See screenshot below for example, it's not 100% correct to be working as described here but it's just for illustration. I was able to confirm my custom events triggered in GTM via the dataLayer.push() are passed to GA4.
Here's how I push the event data to the dataLayer:
dataLayer.push({
"event": "GA4Event",
"ga4EventName": "view_item_list",
"ga4EventParameters": {
"item_list_name":"Homepage",
"items":[{
"item_id":"8888888",
"item_name":"A product",
"item_brand":"a brand",
"price":220.4892,
"currency":"USD",
"index":0
}]
}
});
My gripe with this is that I need to list all event parameters (attributes from the ga4EventParameters object) one by one in the GA4 event tag configuration. See section circled in red in the screenshot. I honestly don't want to do this. It is tedious (we have multiple custom events with their own attributes), and will break as soon as we introduce a new attribute and we don't put it in that list.
I've created my own tag template to make it so that the entire ga4EventParameters object gets passed to GA4 without having to list all of its attributes. It works some of the time, but not all the time, which is where I need help.
Here's the template code (short version, removed init. code and imports):
const gtag = copyFromWindow('gtag');
// The GA4 tag id
const measurementId = data.measurementId;
if (measurementId) {
const ga4EventName = copyFromDataLayer('ga4EventName');
if (ga4EventName) {
let ga4EventParameters = copyFromDataLayer('ga4EventParameters');
if (!ga4EventParameters) {
ga4EventParameters = {};
}
// Set the GA4 property id to send the custom event to
ga4EventParameters.send_to = measurementId;
log('GA4 event data:', ga4EventName, ga4EventParameters, gtag);
if (gtag) {
gtag('event', ga4EventName, ga4EventParameters);
log('gtag called');
} else {
log('gtag not avail.');
}
}
} else {
log('Tag is missing at least one of its configuration parameters');
}
My issue is with gtag, it's only present in window once all the GTM events have occurred:
- If I run a
dataLayer.push()with my custom event once the page is entirely loaded and GTM has completely run (like after 3-4seconds), it works completely fine, everything's great. - However, I have
dataLayer.push()in the HTML of my page. This means it gets executed early. My custom GTM tag gets triggered by it, butgtagdoesn't exist inwindowyet, so I can't send the event to GA4.
I tried triggering my custom tag template on the Window Loaded event that GTM has, but same thing, gtag isn't available yet.
I'm at a loss, I don't know how to solve this. Sure I could just use setTimeout to delay my dataLayer.push() call on the page but it would be unreliable as that delay would vary depending on device, network speed and script execution speed.
On a side note, I have no idea what script exposes gtag to window, and no idea why it gets set so late.

gtagshould not be init so late and just wonder why need to copy from window for gtag function. It should be access without copy when init done.gtaganywhere. If I remove thecopyFromWindow, then GTM complains thatgtagis undeclared. I was counting ongtagto be initialized by GTM, so that I could just use it easily without having to do any manual initialization. But it looks like it's not possible. It's frustrating because in Tag Assistant, I can see that native GTM events likeSetorConsent Defaultdo usegtagwithout any issue but I don't know how they do it.