Key takeaways
- gtag.js is Google's universal tag — it handles GA4, Google Ads, and other Google products from a single snippet
- Place the gtag.js snippet as high as possible in the <head> — before any other scripts that depend on it
- The gtag('config') call sends an automatic page_view — disable it if you are managing page views manually
- Custom events use gtag('event') with an event name and optional parameters — keep event names lowercase with underscores
- Use GTM instead of direct gtag.js if you need flexibility to change tracking without code deployments
In this article
gtag.js is the direct installation method for GA4 — no tag manager, no intermediary layer, just a script tag in your HTML that connects your website to Google's data collection infrastructure. It is the fastest way to get GA4 running, and in certain situations it is the right choice. In most others, it is not.
This guide explains exactly how gtag.js works, what every line of the snippet does, how to send custom events, and how to make an informed decision about whether gtag.js or GTM is the right approach for your implementation.
What gtag.js actually is
gtag.js is a JavaScript library that provides a unified API for all Google measurement and advertising products. When you call gtag(), you are using a single function that can send data to GA4, Google Ads, Google Marketing Platform, and other Google services — all through the same snippet, configured with different product IDs.
It replaced the older analytics.js (Universal Analytics library) and is itself now the recommended direct-installation method. If you have an existing Universal Analytics implementation using analytics.js, that is a different, older library and needs to be replaced — not updated — for GA4.
gtag.js is not the same as Google Tag Manager. GTM is a tag management system that can load gtag.js (and many other tags) on your behalf. When people say "install GA4 via GTM", they mean GTM is loading and configuring gtag.js — not that GTM replaces it. The underlying library is the same either way.
The installation snippet — line by line
Here is the standard GA4 gtag.js snippet with every line explained:
<!-- Step 1: Load the gtag.js library asynchronously --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"> </script> <script> // Step 2: Initialise the dataLayer array if it doesn't exist window.dataLayer = window.dataLayer || []; // Step 3: Define the gtag function // Arguments object is used so all parameters are passed correctly function gtag(){ dataLayer.push(arguments); } // Step 4: Set the initial timestamp // Required — tells GA4 when the library was initialised gtag('js', new Date()); // Step 5: Configure your GA4 property // This also sends an automatic page_view event gtag('config', 'G-XXXXXXXXXX'); </script>
A few things worth noting about this snippet:
- The
asyncattribute on the script tag means the library loads without blocking page rendering — this is correct and should always be present - The
window.dataLayer = window.dataLayer || []line initialises the dataLayer if it does not already exist — safe to have even if you are also using GTM - The
gtag('js', new Date())call is required — without it, GA4 cannot correctly attribute session timing - The
gtag('config')call automatically fires apage_viewevent — you need to be aware of this if you are managing page views manually
Where to place the snippet
Place both script tags as high as possible in the <head> section of every page — immediately after the opening <head> tag, before any other scripts. This ensures:
- GA4 starts initialising as early as possible in the page load
- The
page_viewevent is not delayed by other scripts - gtag is available to any other scripts on the page that might call it
<html> <head> <!-- gtag.js goes FIRST, before other scripts --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){ dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-XXXXXXXXXX'); </script> <!-- Other scripts and meta tags follow --> <meta charset="UTF-8"> <title>Your Page Title</title> </head>
Configuration options
The gtag('config') call accepts an optional third argument — a configuration object — that lets you customise GA4 behaviour:
gtag('config', 'G-XXXXXXXXXX', { // Disable automatic page_view on config load // Use this when you want to fire page_view manually send_page_view: false, // Override the page title sent to GA4 page_title: 'Custom Page Title', // Override the page path page_path: '/custom/path', // Set cookie expiry in seconds (default: 63072000 = 2 years) cookie_expires: 63072000, // Restrict cookies to a specific domain cookie_domain: 'yourdomain.com', // Enable debug mode — sends events to DebugView debug_mode: true // remove before production });
Setting send_page_view: false means GA4 will not record any page views unless you manually fire them using gtag('event', 'page_view'). This is useful for single-page applications where you control when page views are sent — but if you forget to fire the manual page view, you will have sessions with no page view events, which breaks session metrics.
Sending custom events
Custom events use the gtag('event') call with an event name and optional parameters:
// Basic custom event — no parameters gtag('event', 'button_click'); // Custom event with parameters gtag('event', 'form_submit', { form_name: 'contact_form', form_destination: 'sales_team' }); // E-commerce purchase event gtag('event', 'purchase', { transaction_id: 'T-12345', value: 4999, currency: 'INR', items: [{ item_id: 'SKU-001', item_name: 'Product Name', price: 4999, quantity: 1 }] }); // Manual page_view (for SPAs) gtag('event', 'page_view', { page_title: 'New Page Title', page_location: window.location.href });
Event naming rules that matter for GA4 reporting:
- Use lowercase letters, numbers, and underscores only
- No spaces — use underscores instead (
button_clicknotbutton click) - Maximum 40 characters for event names
- Do not use reserved event names like
click,download,scrollfor custom events - Parameter names follow the same rules — lowercase with underscores, max 40 characters
Setting user properties
User properties persist across sessions and allow you to segment your reports by user characteristics. Set them using gtag('set'):
// Set user properties that persist across the session gtag('set', 'user_properties', { membership_tier: 'premium', preferred_language: 'en', account_type: 'business' }); // Set user ID for cross-device tracking // Use a hashed or anonymised ID — never raw PII gtag('config', 'G-XXXXXXXXXX', { user_id: 'hashed_user_id_here' });
Never pass real email addresses, phone numbers, names, or any personally identifiable information as user properties or event parameters. GA4's terms of service prohibit sending PII. Use hashed or anonymised identifiers only.
Using gtag.js with multiple Google products
One advantage of gtag.js over the older analytics.js is that a single snippet can configure multiple Google products. Add additional gtag('config') calls for each product:
gtag('js', new Date()); // GA4 property gtag('config', 'G-XXXXXXXXXX'); // Google Ads conversion tracking gtag('config', 'AW-XXXXXXXXXX'); // Floodlight (Campaign Manager) gtag('config', 'DC-XXXXXXXXXX');
gtag.js vs GTM — when to use each
| Situation | Use gtag.js directly | Use GTM |
|---|---|---|
| Simple site, GA4 only | ✓ Simpler setup | Overkill for basic needs |
| Need to add other tags later | Requires code access each time | ✓ Add tags without code changes |
| Custom event tracking | Requires code changes per event | ✓ Configure in GTM interface |
| Non-technical team managing tags | Not suitable | ✓ GTM interface accessible to non-devs |
| SPA / React / Next.js app | ✓ Easier direct integration | Needs extra configuration for SPAs |
| Multiple Google + third-party tags | Gets complex quickly | ✓ Centralised tag management |
| Speed of iteration on tracking | Slow — needs dev deployment | ✓ Fast — publish from GTM interface |
The honest answer for most professional implementations: use GTM. The direct gtag.js approach makes sense for simple personal projects, prototypes, or React/Next.js applications where you are already managing state and want direct control over when events fire. For anything that needs to change over time, GTM is the right choice.
Debugging your implementation
To enable debug mode temporarily for DebugView, add debug_mode: true to your config call. Remove it before going to production — debug events are sampled differently and can skew your data if left enabled.
// Add during testing, remove before production gtag('config', 'G-XXXXXXXXXX', { debug_mode: true });
The fastest way to confirm gtag.js is working correctly: open Chrome DevTools, go to the Network tab, filter by "collect", and reload the page. You should see a POST request to www.google-analytics.com/g/collect within the first second of page load. If you see that request, GA4 is receiving your data.