Skip to content

Container Installation

Installing the GTM container is the first thing you do, and it’s also the thing most frequently done wrong. A misplaced snippet, a modified script tag, or a duplicate container can cause hours of debugging later. This guide covers the correct installation method for every common platform and explains why placement matters.

Google Tag Manager gives you two code blocks when you create a container. Both are required, and both serve distinct purposes.

The first block goes in the <head>:

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXX');</script>
<!-- End Google Tag Manager -->

The second block goes immediately after the opening <body> tag:

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

The <head> script does the actual work. It creates the dataLayer array (if it doesn’t already exist), fires the gtm.js initialization event, then asynchronously loads your container JavaScript from Google’s servers. Placing it in the <head> ensures GTM loads as early as possible — before the user sees any content, meaning tags that need to fire on page load are not delayed.

The <noscript> block is a fallback for browsers with JavaScript disabled. In practice, this accounts for a tiny fraction of modern traffic, but it satisfies some compliance requirements and should be included. It does nothing useful in JavaScript-enabled browsers.

One critical thing the GTM snippet documentation does not emphasize enough: if you need to push data to the dataLayer before GTM loads — which you should, for consent defaults and initial page data — you must declare the array first:

<script>
window.dataLayer = window.dataLayer || [];
// Push initial data BEFORE the GTM snippet
dataLayer.push({
page_type: 'product',
user_logged_in: false
});
</script>
<!-- GTM snippet goes here -->
<script>(function(w,d,s,l,i)...</script>

The window.dataLayer = window.dataLayer || [] pattern ensures the array exists. The GTM snippet also initializes this array if it doesn’t exist, but by then it might be too late for any pre-GTM pushes you needed. This pattern is especially important for Consent Mode defaults (covered in detail in the Google Consent Mode article).

Add the <head> snippet inside your <head> element, and the <noscript> snippet immediately after <body>:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Site</title>
<!-- Consent defaults (if using Consent Mode) -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
'ad_storage': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500
});
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXX');</script>
<!-- End Google Tag Manager -->
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<!-- Your page content -->
</body>
</html>

Before you declare installation complete, verify that GTM is actually loading and running correctly. There are three ways to do this.

Open DevTools and run this in the console:

// Check 1: Is the dataLayer array present?
console.log(window.dataLayer);
// Expected: Array with at least one object containing gtm.start
// Check 2: Has GTM initialized?
console.log(window.google_tag_manager);
// Expected: Object with your container ID as a key
// Check 3: Which events have fired?
window.dataLayer.forEach((item, i) => {
if (item.event) console.log(i, item.event);
});
// Expected: 'gtm.js', then 'gtm.dom', then 'gtm.load'

If window.google_tag_manager is undefined, GTM has not loaded. Check for network errors in the Network tab.

In DevTools Network tab, filter by “gtm.js”. You should see a request to www.googletagmanager.com/gtm.js?id=GTM-XXXX with a 200 status code. If you see a 404, your container ID is wrong. If you see nothing at all, the snippet is not installed.

Install the Google Tag Assistant browser extension. Navigate to your site, click the extension icon, and it will show you which Google tags are active and whether they are configured correctly. It is the fastest way to spot common installation problems.

The most common mistake is placing the <head> snippet at the bottom of <head> or in <body>, and placing the noscript block somewhere other than immediately after <body>. GTM’s own guidance is clear: head snippet as early as possible in <head>, noscript immediately after <body>.

Do not add defer, async, or any other attributes to the script tag created by the GTM snippet. The snippet already manages its own async loading. Adding defer to the outer <script> wrapper will delay the entire initialization. Adding async to an already-async pattern has no effect. Either way, you are changing behavior GTM expects to control.

If you see GTM loading twice in the Network tab, you have a duplicate installation. This can happen when a plugin adds GTM and you also have it hardcoded, or when you switch themes and the old theme still has the snippet. Duplicate containers cause double-counted pageviews and event data. Audit all locations where GTM could be installed.

Copying the snippet from a GTM tutorial and forgetting to replace GTM-XXXX with your actual container ID is embarrassingly common. The container will “load” (no JS errors) but send data nowhere. Your container ID is visible in GTM’s admin area and looks like GTM-XXXXXXX.

If your site has the GTM snippet installed, do not also install the standalone gtag.js snippet. GTM loads its own Google Tag internally. Having both creates duplicate instances that can conflict, double-count conversions, and cause consent mode to behave unpredictably.