GTM Tags, Triggers, and Variables: The Three Building Blocks

Google Tag Manager operates on three elements: tags, triggers, and variables. They enable detailed tracking setups where tags send data, triggers define firing conditions, and variables maintain flexibility in data handling.

Google Tag Manager looks intimidating from the outside, with its panels and templates and publishing workflows. But strip everything away and there are exactly three primitives underneath: tags, triggers, and variables. Every tracking setup ever built, no matter how sprawling, is just these three combined. A tag is the action, the code that sends data somewhere. A trigger is the when, the condition that fires the tag. A variable is the what, a named value passed around between them.

The relationships matter as much as the definitions. A tag without a trigger never fires. A trigger without a tag does nothing. And variables are how the other two stay flexible instead of having values hardcoded into every corner of the container. Once that clicks, the rest of GTM is just detail.

Tags: The Code That Sends Data Out

A tag is a piece of code GTM injects into the page when its trigger condition is met. Most of the time you never write that code yourself; you pick a template like “GA4 Event” or “Meta Pixel” and fill in fields. Analytics tags send pageviews, events, and conversions to platforms like GA4 or Adobe Analytics. Advertising tags like Google Ads Conversion, Meta Pixel, and LinkedIn Insight handle conversion tracking and retargeting audiences. Third-party templates cover tools like Hotjar and Optimizely. And when no template exists, Custom HTML lets you inject an arbitrary script block.

Here is what a GA4 purchase event tag looks like in the GTM interface:

Tag type: Google Analytics: GA4 Event
Measurement ID: G-XXXXXXXXXX (or use a Constant variable)
Event Name: purchase (or {{Event}} to inherit from the dataLayer)
Event Parameters:
transaction_id → {{DLV - transaction_id}}
value → {{DLV - value}}
currency → {{DLV - currency}}
items → {{DLV - items}}
Triggering: [Purchase Trigger]

The Measurement ID tells GA4 which property receives the hit. The event name is what GA4 records, and sticking to GA4’s recommended names in snake_case means the data lands in standard reports automatically. The parameters are key-value pairs populated by variables, and each {{DLV - ...}} reference is a Data Layer Variable reading a value the site pushed into the dataLayer moments earlier.

When templates run out of road, Custom HTML steps in:

<script>
(function() {
var orderValue = {{DLV - ecommerce.value}};
if (orderValue > 500) {
console.log('High-value transaction:', orderValue);
window.dataLayer.push({ event: 'high_value_purchase' });
}
})();
</script>

The wrapping function is an IIFE, which keeps the variables out of the global scope where they could collide with the site’s own JavaScript. GTM substitutes {{DLV - ecommerce.value}} with the real dataLayer value at fire time, the conditional applies logic that no template can express, and the final dataLayer.push sends a brand new event back into the dataLayer, where it can fire its own trigger and tag in turn. That loop is one of GTM’s most powerful patterns.

A word of caution though: reach for Custom HTML only when nothing else works. Templates are sandboxed, easier to audit, and far harder to break a site with.

Triggers: The Conditions That Fire Tags

A trigger is a set of conditions, and when those conditions are met, every tag attached to the trigger fires. GTM ships a generous set of trigger types. Page View, DOM Ready, and Window Loaded fire at successive stages of the page loading. Click triggers come in two flavors, one for any element and one restricted to links. Form Submission catches successful form submits. Element Visibility fires when something scrolls into the viewport, Scroll Depth fires at percentage thresholds, Timer fires after an interval, History Change catches single-page-app navigation where the URL changes without a reload, and Custom Event listens for anything pushed to the dataLayer.

Every trigger except a bare “All Pages” carries a filter, and the format never changes: a variable, an operator, and a value. Page Path equals /checkout/thank-you isolates a confirmation page. Page URL contains utm_source=newsletter isolates newsletter traffic. Click Classes contains cta-primary isolates your main call-to-action buttons.

Here is a trigger that catches PDF download clicks:

Trigger Type: Click - Just Links
This trigger fires on: Some Link Clicks
Conditions:
Click URL matches RegEx (ignore case) \.pdf(\?.*)?$

Three small decisions make this trigger good rather than merely functional. “Just Links” restricts listening to anchor tags, which is cheaper than watching every element on the page. “Some Link Clicks” is what lets you apply the filter at all; “All Link Clicks” would fire on every link on the site. And the regex \.pdf(\?.*)?$ matches .pdf at the end of the URL while tolerating an optional query string, with the ignore-case flag catching .PDF files uploaded by someone’s marketing intern.

The other trigger you will use constantly is the Custom Event:

Trigger Type: Custom Event
Event name: purchase
This trigger fires on: All Custom Events

This listens for dataLayer.push({ event: 'purchase' }) coming from the site’s code. One detail bites everyone eventually: the event name must match the dataLayer push exactly, including case. Purchase and purchase are different events as far as GTM is concerned.

Two refinements round out the trigger story. Trigger Groups fire a tag only when several triggers have all fired in a session, useful for things like “scrolled 75% and clicked the CTA.” Trigger Exceptions attach to the tag rather than the trigger and prevent firing, the classic case being “fire everywhere except the staging site.”

Variables: The Named Values That Keep Everything Flexible

A variable is a placeholder GTM resolves at firing time. GTM ships with built-in variables you enable with a checkbox: page variables like Page URL and Page Path, click variables like Click URLClick Text, and Click Classes, form variables, video variables, scroll variables, and the special Event variable holding the current dataLayer event name.

Beyond the built-ins, you define your own. The Data Layer Variable reads a key from the dataLayer. A Constant holds a hardcoded reusable value, perfect for a Measurement ID you reference from twenty tags. Custom JavaScript runs a function and returns its result. Lookup Tables and RegEx Tables map inputs to outputs. First-Party Cookie variables read cookie values, and DOM Element variables pull content straight off the page by CSS selector.

The Data Layer Variable is the workhorse:

Variable Type: Data Layer Variable
Data Layer Variable Name: ecommerce.transaction_id
Data Layer Version: Version 2
Default Value: (not set)

The name field accepts dot notation, so it can reach into nested objects. Version 2 is the modern API and the one you should always use. The default value is what comes back when the key does not exist, and setting it to something explicit beats silently getting undefined in your reports.

When you need a computed value, Custom JavaScript does the job:

function() {
var url = {{Page URL}};
return url.split('?')[0].toLowerCase();
}

GTM expects an anonymous function and uses whatever it returns as the variable’s value. This one strips the query string by splitting on ? and keeping the first piece, then lowercases the result so /Home and /home count as the same page in your reports.

And for mapping values to categories, the Lookup Table replaces what would otherwise be a chain of if/else statements:

Variable Type: Lookup Table
Input Variable: {{Page Path}}
Lookup Table:
/ → Homepage
/products → Product Listing
/cart → Cart
/checkout → Checkout
Default Value: Other

Each row is an exact-match mapping from input to output, with the default catching everything else. Lookup tables have a quiet superpower: non-developers can read and edit them, which means the marketing team can maintain the page-section mapping without filing a ticket.

How the Three Fit Together

The firing flow runs the same way every time. A user does something on the page. The browser produces an event, either a DOM event like a click or a dataLayer push. The GTM container script sees it and evaluates every trigger, resolving the variables in each trigger’s conditions and checking whether they match. For every trigger that matches, GTM fires the attached tags, resolving the variables in each tag’s configuration and executing the code. And if a tag pushes a new event to the dataLayer, the whole cycle starts again from the top.

The consequence worth tattooing somewhere visible: variables are evaluated at fire time, not at page load. A Data Layer Variable reads whatever was most recently pushed before the trigger fired. This is why event parameters sometimes arrive empty: the tag fired before the data it needed existed.

A Complete Walkthrough: Tracking PDF Downloads in GA4

Theory is nice; here is the whole thing end to end.

First, enable the built-in click variables under Variables, then Configure: Click URLClick Text, and Click Classes.

Second, create the trigger:

Name: PDF - Click
Trigger Type: Click - Just Links
This trigger fires on: Some Link Clicks
Filter: Click URL matches RegEx (ignore case) \.pdf(\?.*)?$

Third, create the tag:

Name: GA4 - File Download - PDF
Tag Type: Google Analytics: GA4 Event
Configuration Tag: {{GA4 Config}}
Event Name: file_download
Event Parameters:
file_name {{Click Text}}
file_url {{Click URL}}
file_extension pdf
Triggering: PDF - Click

The event name file_download is GA4’s recommended name for exactly this, and file_name and file_url are recommended parameters that surface in standard reports without extra configuration. The extension is hardcoded to pdf because the trigger guarantees it. {{Click Text}} captures the visible link text, which is usually the document’s title and far more readable in reports than a URL.

Fourth, test before anything goes live. Click Preview in GTM, enter your site URL, and Tag Assistant opens alongside your site. Click a PDF link and confirm the trigger fired and the tag fired. Then check GA4’s DebugView to confirm the file_download event arrived with its parameters intact. Only after all of that, hit Submit, give the version a descriptive name, and publish.

Naming Conventions Are a Gift to Your Future Self

Six months from now, someone (probably you) will open this container with no memory of what anything does. Consistent names are the difference between a five-minute audit and an afternoon of clicking. A format that works well: tags as [Platform] - [Event] - [Detail], like GA4 - Purchase or Meta - AddToCart. Triggers as [Type] - [Detail], like CE - purchase or Click - PDF Download. Variables as [Type] - [Source], like DLV - transaction_id or JS - Page Section. The short prefixes pay off fast: DLV for Data Layer Variable, JS for Custom JavaScript, LT for Lookup Table, CE for Custom Event, Const for Constant.

Choosing the Right Trigger

Match the trigger to the user action. Page loads get Page View, DOM Ready, or Window Loaded depending on what the tag needs available. Clicks get a click trigger, preferring Just Links when the target is an anchor. Form submits get Form Submission, things appearing on screen get Element Visibility, scrolling gets Scroll Depth, videos get the YouTube trigger, time on page gets Timer, and single-page-app navigation gets History Change.

But the rule of thumb that overrides all of it: if developers are pushing events to the dataLayer, prefer Custom Event triggers. A dataLayer event is an explicit contract between the site and your container. A DOM-based trigger is a guess about the site’s HTML structure, and it silently breaks the day a developer renames a CSS class without telling anyone.

Practices That Keep a Container Healthy

A few habits separate maintainable containers from haunted ones. Store IDs like the GA4 Measurement ID in Constant variables so there is exactly one place to update them. Use a single GA4 Configuration tag and reference it from every event tag, centralizing settings like cookie domain. Add trigger exceptions for staging hostnames so test data never pollutes production analytics. Group tags into folders by platform. Name versions descriptively, like 2026-06-11 - Add PDF tracking, because the day something breaks, a clear version history makes rollback a thirty-second job. Keep Custom HTML tags to a minimum since they can execute anything and are both a security and a performance liability. And test every single change in Preview, because GTM publishes globally and instantly; there is no gradual rollout to save you from a typo.

The Pitfalls Everyone Hits Once

A few failure modes account for most GTM debugging sessions. A trigger set to All Elements without a filter fires on every click and inflates event counts; use Some Click Events with a specific condition. Form Submission triggers never fire on forms submitted via AJAX, which is most modern forms; switch to a Custom Event pushed by the developers. A variable returning undefined and filling GA4 with “(not set)” is almost always a spelling or casing mismatch in the Data Layer Variable name. Events arriving without their parameters mean the tag fired before the dataLayer push it depended on; a Custom Event trigger that fires after the push fixes the ordering. Tags firing twice on a confirmation page double-count purchases, which is what transaction_id deduplication in GA4 exists for. And test data flowing in from staging is solved with one trigger exception on Page Hostname.

Each of these traces back to the same three primitives. When something misbehaves, the question is always one of three: is the tag configured correctly, is the trigger condition what I think it is, or is the variable resolving to the value I expect at the moment of firing? Answer those in order and there is no GTM mystery that survives long.

See you soon.

View Comments (2)

Leave a Reply

Prev Next

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Datalad - Data Science and ML

Subscribe now to keep reading and get access to the full archive.

Continue reading