Where to Place the Google Tag Manager Snippet (and Why It Matters)
Last week I audited a DTC skincare brand running EUR 11,000 a month across Google and Meta. GA4 showed a 22 percent drop in sessions over the past six weeks. No seasonal pattern, no campaign pause, no site migration. The marketing team blamed iOS changes. The developer blamed a GA4 update.
It took me four minutes to find the actual cause. A Shopify theme update had moved the google tag manager snippet from <head> to a position deep inside <body>, after a lazy-loaded hero image and two render-blocking stylesheets. The container was still loading -- but late. On mobile connections, the DOM Interactive event fired before the GTM script had finished parsing. Tags tied to the Page View trigger were missing roughly one in five sessions. Six weeks of under-counted data, six weeks of Smart Bidding training on a distorted signal.
This is a placement problem. The snippet was on the page. It was the right container ID. But it was in the wrong place, and "wrong place" is enough to quietly break everything downstream.
What the Google Tag Manager Code Snippet Actually Contains
Before choosing where to put it, you need to understand what it does. The google tag manager code snippet has two parts. Google's official installation instructions are explicit about both.
Part 1: The <script> block. This is an inline JavaScript snippet that creates the dataLayer array (if it does not already exist), pushes a gtm.start timestamp, and asynchronously loads your container's JavaScript file from https://www.googletagmanager.com/gtm.js. The async attribute means it does not block HTML parsing -- but the sooner the browser encounters it, the sooner it starts downloading.
Part 2: The <noscript> iframe. This loads a fallback version of the container for environments where JavaScript is disabled or blocked. It fires a single pageview-like hit via an iframe pointing to https://www.googletagmanager.com/ns.html.
Here is a google tag manager snippet example showing both parts with a placeholder container ID:
<!-- Google Tag Manager - Part 1: place in <head> -->
<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-XXXXXXX');</script>
<!-- End Google Tag Manager -->
<!-- Google Tag Manager (noscript) - Part 2: place after opening <body> -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
Replace GTM-XXXXXXX with your actual container ID from the GTM admin panel.
Head vs Body: What Google Says and Why It Is Not Optional
Google's documentation is unambiguous. The <script> block goes "as high in the <head> of the page as possible." The <noscript> goes "immediately after the opening <body> tag." These are not suggestions.
The reason is timing. Every tag inside your GTM container -- GA4 pageviews, Google Ads remarketing, Meta Pixel, consent defaults -- depends on the container loading first. If the container loads late, tags fire late. If tags fire late, three things break:
1. Consent Mode defaults do not set in time
If you use Consent Mode v2, the default consent state must be established before any Google tag fires. Google's Consent Mode documentation requires that gtag('consent', 'default', {...}) runs before the GTM container loads. If the container snippet is buried in <body>, the default consent command in your template tag fires after other scripts have already tried to set cookies. The result: tags either fire without proper consent handling (a compliance risk in GDPR markets) or get blocked entirely because the consent state was not established in time.
2. Early pageview and session data is lost
GA4's Page View event fires on the Container Loaded trigger by default. If the container loads two or three seconds into the page lifecycle, you miss visitors who bounce within that window. On mobile, where 53 percent of visits are abandoned if a page takes longer than three seconds to load, a late-loading container can miss a meaningful share of sessions.
3. Conversion tags fire after the user has navigated away
On fast-loading pages with short conversion flows -- think a click-through landing page with a "Buy Now" button -- a container that loads late may not have finished initializing before the user clicks and navigates to the next page. The conversion tag never fires. Your Google Ads conversion tracking shows fewer conversions than actually happened. Smart Bidding pulls back spend on a campaign that was working.
The Placement Rules by Platform
Where you physically paste the google tag manager snippet depends on your CMS or framework. The principle is always the same -- <script> high in <head>, <noscript> right after <body> -- but the mechanics vary.
| Platform | Where to add <script> | Where to add <noscript> | Guide |
|---|---|---|---|
| WordPress | Child theme functions.php via wp_head hook (priority 1) | wp_body_open hook | WordPress install guide |
| Shopify | theme.liquid, top of <head> | theme.liquid, right after <body> | Shopify conversion tracking |
| Squarespace | Settings > Advanced > Code Injection > Header | Code Injection > Footer (closest available) | Squarespace tracking |
| Webflow | Project Settings > Custom Code > Head Code | Body Code | Webflow guide |
| Wix | Settings > Custom Code > Head | Body - start | Wix install guide |
| Next.js | Root layout via @next/third-parties component | Handled by the component | Next.js guide |
| Raw HTML | Paste directly in <head> | Paste directly after <body> | See the example above |
On platforms like Squarespace that do not expose the <body> tag directly, you lose the <noscript> fallback. For most modern tracking purposes this is acceptable -- the <noscript> iframe only matters for users with JavaScript completely disabled, which is a fraction of a percent of traffic. The <head> placement of the main script is the critical piece.
Five Placement Mistakes I Find in Audits
These are ordered by frequency. Most broken containers I audit have at least one.
Mistake 1: Snippet placed at the bottom of <body>
A developer puts the snippet before the closing </body> tag "to avoid blocking rendering." The GTM <script> already uses async loading, so it does not block HTML parsing. Placing it at the bottom just delays discovery. The browser does not even know the script exists until it has parsed the entire document. On a content-heavy page, that can be seconds of delay before the container starts loading.
Mistake 2: Snippet placed inside a deferred script bundle
Build tools like Webpack, Vite, or Parcel sometimes ingest inline scripts and bundle them with application code. If your GTM snippet gets swept into a deferred or lazy-loaded bundle, it loads after the main application JavaScript -- far too late for consent defaults or initial pageviews. The GTM snippet should always be a standalone inline script, never imported or bundled.
Mistake 3: Two containers loading on the same page
A plugin adds one container. A developer manually pastes another. Or the same container ID is loaded twice because nobody checked. Two loads of the same container mean every tag fires twice. GA4 records double pageviews. Conversion counts inflate. I wrote about diagnosing this with browser console checks in the GTM debug checklist.
Mistake 4: Missing the <noscript> part entirely
The developer copies only the <script> block and skips the <noscript> iframe. In practice, this rarely causes measurable data loss because almost no modern browser runs with JavaScript fully disabled. But it does cause GTM's container installation check to flag your site as improperly configured, which creates noise in audits and may confuse future developers.
Mistake 5: CMS or plugin moving the snippet after updates
This is the failure I opened this post with. Theme updates, plugin updates, and CMS platform changes can silently relocate or remove the snippet. The fix is a monitoring habit: after every deployment, verify the snippet is present and positioned correctly. A quick console check -- type google_tag_manager in the browser console and confirm it returns an object -- takes five seconds and catches the problem before six weeks of data are lost.
If any of these sound familiar and you are not confident your setup is correct, I can audit your container and tell you exactly what needs fixing.
How to Verify Your Snippet Placement
After installing or moving your google tag manager snippet, run through this verification sequence.
Step 1: View page source
Load your site in an incognito window, right-click, select "View Page Source," and search for your container ID (e.g., GTM-XXXXXXX). Confirm the <script> block appears inside <head> and the <noscript> appears immediately after <body>. If either is missing or misplaced, fix the placement before doing anything else.
Step 2: Check for duplicates
Search the page source for your container ID. It should appear exactly twice -- once in the <script> and once in the <noscript>. Four occurrences means the container loads twice. This is common on WordPress sites where a plugin and a manual install coexist.
Step 3: Test in GTM Preview mode
In your GTM workspace, click Preview and enter your site URL. Tag Assistant should connect and show the Container Loaded event. If it does not connect, the container is not loading. Go back to the source and check placement. For a full verification workflow, I walk through every step in the GTM debug checklist.
Step 4: Test on mobile
Desktop verification is not enough. Mobile browsers on slower connections are where placement problems surface. Use Chrome DevTools device emulation with throttled network speed (Slow 3G) and confirm the container loads before DOM Interactive. If it does not, your <head> placement may be correct but blocked by a render-blocking resource above it -- move the GTM snippet higher.
Does Placement Affect Page Speed?
A common concern: "Will putting a script in <head> slow down my page?" For GTM specifically, no. The google tag manager code snippet uses the async attribute, which means the browser downloads the script in parallel with HTML parsing and executes it when ready. It does not block rendering.
That said, what you load inside the container can absolutely affect page speed. A container stuffed with 30 marketing pixels, each loading its own external JavaScript file, will slow things down regardless of where the container snippet sits. The solution is not moving the snippet -- it is cleaning the container. Audit what is inside, remove tags you are not actively using, and consider server-side tracking for heavy pixels that do not need to run in the browser.
FAQ
Does the Google Tag Manager snippet go in the head or body?
The main script block goes as high as possible inside the head element. The noscript iframe goes immediately after the opening body tag. Google's official documentation is explicit about both placements, and deviating from them risks delayed tag firing or missed data.
Will putting the GTM snippet in the head slow down my site?
No. The GTM script uses the async attribute, which means the browser downloads it in parallel with HTML parsing without blocking page rendering. The snippet itself is a few hundred bytes of inline JavaScript. What can slow your site is loading too many heavy tags inside the container, not the container snippet placement.
What happens if I only add the script part and skip the noscript?
You lose the fallback for visitors with JavaScript disabled, which is a very small percentage of modern traffic. Practically, most tracking will still work. However, GTM's built-in installation checker will flag your site as improperly configured, and you lose a data signal for the rare noscript visitor.
Can I place the GTM snippet inside a JavaScript framework component?
Yes, but you need to ensure it loads exactly once and does not re-execute on client-side route changes. Frameworks like Next.js and Nuxt have specific patterns for this. Loading the snippet inside a component that remounts on navigation will cause duplicate container loads and double-fired tags.
How do I check if my GTM snippet is installed correctly?
Open your site in an incognito browser window, view the page source, and search for your container ID. It should appear twice, once in the head script and once in the body noscript iframe. Then open the browser console and type google_tag_manager to confirm the container object exists. Finally, use GTM Preview mode to verify tags are firing as expected.
Not sure your snippet placement is right -- or whether the tags inside are actually firing? Let me audit your setup and tell you exactly what to fix.